Разница между Collections.defaultdict и обычным dict в Python
Я изучил примеры из документации по 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 ответ(ов)
Обычно при попытке получить элемент из словаря Python с помощью ключа, который отсутствует в словаре, возникает ошибка KeyError
. Однако defaultdict
ведет себя иначе: он автоматически создаёт элементы, к которым вы пытаетесь получить доступ, если их еще нет. Для создания такого "значения по умолчанию" defaultdict
вызывает функцию, которую вы передаёте в конструктор (точнее, это может быть любой "вызываемый" объект, включая функции и типы объектов).
Например, в первом случае значения по умолчанию создаются с помощью int()
, что возвращает целочисленный объект 0
. Во втором случае значения по умолчанию создаются с помощью list()
, который возвращает новый пустой объект списка.
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
очень удобным для работы с данными, где можно заранее задать значение по умолчанию для несуществующих ключей.
Словари — это удобный способ хранения данных для последующего извлечения по имени (ключу). Ключи должны быть уникальными и неизменяемыми объектами, чаще всего это строки. Значения в словаре могут быть любыми. Для многих приложений значения представляют собой простые типы, такие как целые числа и строки.
Ситуация становится более интересной, когда значения в словаре — это коллекции (списки, словари и т.д.). В этом случае значение (пустой список или словарь) должно быть инициализировано в первый раз, когда используется данный ключ. Хотя это относительно просто сделать вручную, тип 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
.
В этом ответе хорошо объясняются defaultdict
здесь: http://ludovf.net/blog/python-collections-defaultdict/
По сути, параметры int и list — это функции, которые вы передаете. Помните, что в Python имена функций могут быть аргументами. Функция int возвращает 0 по умолчанию, а list возвращает пустой список, когда её вызывают с круглыми скобками.
В обычных словарях, если в вашем примере вы попытаетесь вызвать d[a]
, вы получите ошибку (KeyError), так как только ключи m, s, i и p существуют, и ключ a не был инициализирован. Однако в defaultdict
, когда вы используете ключ, который не был инициализирован, он просто вызывает переданную функцию и назначает её возвращаемое значение в качестве значения нового ключа.
Поскольку вопрос касается того, "как это работает", некоторым читателям может быть интересно узнать больше деталей. В частности, речь идет о методе __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})
Преобразование списка словарей в DataFrame pandas
Почему использовать dict.get(key) вместо dict[key]?
Как преобразовать вложенный словарь Python в объект?
Ошибка: "'dict' объект не имеет метода 'iteritems'"
Есть ли питоний способ объединить два словаря (сложив значения для ключей, которые присутствуют в обоих)?