8

Разница между Collections.defaultdict и обычным dict в Python

25

Я изучил примеры из документации по Python, но до сих пор не могу понять, что на самом деле означает этот метод. Может, кто-то сможет помочь? Вот два примера из документации:

>>> from collections import defaultdict

>>> s = 'mississippi' >>> d = defaultdict(int) >>> for k in s: ... d[k] += 1 ... >>> d.items() dict_items([('m', 1), ('i', 4), ('s', 4), ('p', 2)])

и

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

Для чего нужны параметры int и list?

5 ответ(ов)

8

Обычно при попытке получить элемент из словаря Python с помощью ключа, который отсутствует в словаре, возникает ошибка KeyError. Однако defaultdict ведет себя иначе: он автоматически создаёт элементы, к которым вы пытаетесь получить доступ, если их еще нет. Для создания такого "значения по умолчанию" defaultdict вызывает функцию, которую вы передаёте в конструктор (точнее, это может быть любой "вызываемый" объект, включая функции и типы объектов).

Например, в первом случае значения по умолчанию создаются с помощью int(), что возвращает целочисленный объект 0. Во втором случае значения по умолчанию создаются с помощью list(), который возвращает новый пустой объект списка.

3

defaultdict — это специальный тип словаря в Python, который позволяет избегать возникновения ошибки KeyError, если ключ не найден в словаре. Вместо этого создаётся новая запись, тип которой определяется аргументом, переданным в defaultdict.

Например:

somedict = {}
print(somedict[3])  # KeyError

someddict = defaultdict(int)
print(someddict[3])  # выведет int(), то есть 0

В первом случае при попытке доступа к несуществующему ключу 3 в обычном словаре somedict возникает ошибка. Во втором случае, используя defaultdict(int), при обращении к несуществующему ключу 3 создаётся новая запись со значением, равным 0 (результат вызова int()). Это делает defaultdict очень удобным для работы с данными, где можно заранее задать значение по умолчанию для несуществующих ключей.

0

Словари — это удобный способ хранения данных для последующего извлечения по имени (ключу). Ключи должны быть уникальными и неизменяемыми объектами, чаще всего это строки. Значения в словаре могут быть любыми. Для многих приложений значения представляют собой простые типы, такие как целые числа и строки.

Ситуация становится более интересной, когда значения в словаре — это коллекции (списки, словари и т.д.). В этом случае значение (пустой список или словарь) должно быть инициализировано в первый раз, когда используется данный ключ. Хотя это относительно просто сделать вручную, тип defaultdict автоматизирует и упрощает подобные операции.

defaultdict работает точно так же, как обычный словарь, но он инициализируется функцией ("фабрикой значений по умолчанию"), которая не принимает аргументов и предоставляет значение по умолчанию для несуществующего ключа.

defaultdict никогда не вызывает ошибку ключа (KeyError). Любой несуществующий ключ получает значение, возвращаемое фабрикой по умолчанию.

from collections import defaultdict
ice_cream = defaultdict(lambda: 'Vanilla')

ice_cream['Sarah'] = 'Chunky Monkey'
ice_cream['Abdul'] = 'Butter Pecan'

print(ice_cream['Sarah'])
>>> Chunky Monkey

print(ice_cream['Joe'])
>>> Vanilla

Вот еще один пример, как использование defaultdict может уменьшить сложность

from collections import defaultdict
# Временная сложность O(n^2)
def delete_nth_naive(array, n):
    ans = []
    for num in array:
        if ans.count(num) < n:
            ans.append(num)
    return ans

# Временная сложность O(n) с использованием хэш-таблиц.
def delete_nth(array,n):
    result = []
    counts = defaultdict(int)

    for i in array:
        if counts[i] < n:
            result.append(i)
            counts[i] += 1
    return result

x = [1,2,3,1,2,1,2,3]
print(delete_nth(x, n=2))
print(delete_nth_naive(x, n=2))

В заключение, когда вам нужен словарь, и значения каждого элемента должны начинаться с значения по умолчанию, используйте defaultdict.

0

В этом ответе хорошо объясняются defaultdict здесь: http://ludovf.net/blog/python-collections-defaultdict/

По сути, параметры int и list — это функции, которые вы передаете. Помните, что в Python имена функций могут быть аргументами. Функция int возвращает 0 по умолчанию, а list возвращает пустой список, когда её вызывают с круглыми скобками.

В обычных словарях, если в вашем примере вы попытаетесь вызвать d[a], вы получите ошибку (KeyError), так как только ключи m, s, i и p существуют, и ключ a не был инициализирован. Однако в defaultdict, когда вы используете ключ, который не был инициализирован, он просто вызывает переданную функцию и назначает её возвращаемое значение в качестве значения нового ключа.

0

Поскольку вопрос касается того, "как это работает", некоторым читателям может быть интересно узнать больше деталей. В частности, речь идет о методе __missing__(key). Более подробную информацию можно найти в документации: https://docs.python.org/2/library/collections.html#defaultdict-objects.

Для конкретного примера использования __missing__(key) в практическом контексте можно ознакомиться с вот этим ответом: https://stackoverflow.com/a/17956989/1593924.

Чтобы пояснить, что означает 'callable', приведу пример интерактивной сессии (на Python 2.7.6, но должно работать и в версии 3):

>>> x = int
>>> x
<type 'int'>
>>> y = int(5)
>>> y
5
>>> z = x(5)
>>> z
5

>>> from collections import defaultdict
>>> dd = defaultdict(int)
>>> dd
defaultdict(<type 'int'>, {})
>>> dd = defaultdict(x)
>>> dd
defaultdict(<type 'int'>, {})
>>> dd['a']
0
>>> dd
defaultdict(<type 'int'>, {'a': 0})

Это самый типичный случай использования defaultdict (за исключением бесполезного использования переменной x). Вы можете сделать то же самое с 0 в качестве явного значения по умолчанию, но не с простым значением:

>>> dd2 = defaultdict(0)

Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    dd2 = defaultdict(0)
TypeError: first argument must be callable

Вместо этого, следующий пример работает, так как мы передаем простую функцию (создается анонимная функция, которая не принимает аргументов и всегда возвращает 0):

>>> dd2 = defaultdict(lambda: 0)
>>> dd2
defaultdict(<function <lambda> at 0x02C4C130>, {})
>>> dd2['a']
0
>>> dd2
defaultdict(<function <lambda> at 0x02C4C130>, {'a': 0})

И с другим значением по умолчанию:

>>> dd3 = defaultdict(lambda: 1)
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {})
>>> dd3['a']
1
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {'a': 1})
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь