Як працює collection.defaultdict?


531

Я читав приклади в документах python, але все ще не можу зрозуміти, що означає цей метод. Може хтось допоможе? Ось два приклади з документів python

>>> from collections import defaultdict

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

і

>>> 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для чого?


15
BTW, залежно від випадку використання, не забудьте заморозити вирок за замовчуванням для використання лише для читання, встановивши його default_factory = Noneпісля того, як ви заповнили заповнення за замовчуванням. Дивіться це питання .
Acumenus

Відповіді:


598

Зазвичай словник Python KeyErrorвидає, якщо ви намагаєтеся отримати елемент з ключем, якого наразі немає у словнику. На defaultdictпротивагу цьому будуть просто створені будь-які елементи, до яких ви намагаєтеся отримати доступ (за умови, що вони ще не існують). Щоб створити такий елемент "за замовчуванням", він викликає об'єкт функції, який ви передаєте конструктору (точніше, це довільний "дзвонючий" об'єкт, який включає об'єкти функції та типу). Для першого прикладу створюються елементи за замовчуванням з використанням int(), які повернуть цілий об'єкт 0. Для другого прикладу створюються елементи за замовчуванням, використовуючи list()який повертає новий об'єкт порожнього списку.


4
Чи функціонально відрізняється від використання d.get (ключ, default_val)?
Амбарееш

29
@Ambareesh d.get(key, default)ніколи не змінює ваш словник - він просто поверне типовий і залишить словник без змін. defaultdictз іншого боку, вставить ключ у словник, якщо його ще немає. Це велика різниця; див. приклади у питанні, щоб зрозуміти, чому.
Свен Марнах

Як ми можемо знати, яке значення за замовчуванням для кожного типу? 0 для int () і [] для list () є інтуїтивно зрозумілими, але можуть бути і більш складні або самостійно визначені типи.
Шон

1
@Sean defaultdictвикликає будь-який конструктор, в який ви переходите. Якщо ви передаєте тип T, значення будуватимуться за допомогою T(). Не всі типи можуть бути побудовані без передачі жодних параметрів. Якщо ви хочете побудувати такий тип, вам потрібна функція обгортки або щось подібне functools.partial(T, arg1, arg2).
Свен Марнах

224

defaultdictозначає, що якщо ключ не знайдений у словнику, то замість KeyErrorвикидання створюється новий запис. Тип цього нового запису задається аргументом вирок за замовчуванням.

Наприклад:

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

someddict = defaultdict(int)
print(someddict[3]) # print int(), thus 0

10
"Тип цієї нової пари задається аргументом вирок за замовчуванням." Зауважте, що аргументом може бути будь-який об’єкт, що викликається - не лише вводити функції. Наприклад, якщо foo була функцією, яка повертає "bar", foo може використовуватися як аргумент диктату за замовчуванням, і якщо доступ до ключа, який не присутній, його значення буде встановлено на "bar".
lf215

13
Або якщо ви просто хочете повернути "бар": somedict = засудження за замовчуванням (лямбда: "бар")
Майкл Скотт Катберт

Четвертий рядок повертає 0ціле число, якщо someddict = defaultdict(list)воно повертається [ ]. Чи 0 за замовчуванням ціле число? Або [] список за замовчуванням?
Gathide

Ні. 0є незмінним - у CPython всі значення від -5до 256кешуються одиночними клавішами, але це поведінка, що залежить від реалізації - в обох випадках новий екземпляр "створюється" кожен раз за допомогою int()або list(). Таким чином, d[k].append(v)можна працювати, не заповнюючи словник посиланнями на той самий список, що було б defaultdictмайже марно. Якби така поведінка, параметр defaultdictприймав би значення, а не лямбда. (Вибачте за жахливе пояснення!)
wizzwizz4

93

вирок за замовчуванням

"Стандартний словник включає метод setdefault () для отримання значення та встановлення за замовчуванням, якщо значення не існує. Навпаки, defaultdictдозволяє абоненту вказати типовий (значення, яке потрібно повернути) вперед, коли контейнер ініціалізується."

як визначено Дугом Гелльманом у стандартній бібліотеці Python за прикладом

Як використовувати дефолт

Імпортувати вирок за замовчуванням

>>> from collections import defaultdict

Ініціалізуйте вирок за замовчуванням

Ініціалізуйте його шляхом проходження

називається як її перший аргумент (обов'язковий)

>>> d_int = defaultdict(int)
>>> d_list = defaultdict(list)
>>> def foo():
...     return 'default value'
... 
>>> d_foo = defaultdict(foo)
>>> d_int
defaultdict(<type 'int'>, {})
>>> d_list
defaultdict(<type 'list'>, {})
>>> d_foo
defaultdict(<function foo at 0x7f34a0a69578>, {})

** kwargs як другий аргумент (необов'язково)

>>> d_int = defaultdict(int, a=10, b=12, c=13)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

або

>>> kwargs = {'a':10,'b':12,'c':13}
>>> d_int = defaultdict(int, **kwargs)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

Як це працює

Оскільки це дочірній клас стандартного словника, він може виконувати всі ті ж функції.

Але у випадку передачі невідомого ключа він повертає значення за замовчуванням замість помилки. Наприклад:

>>> d_int['a']
10
>>> d_int['d']
0
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12, 'd': 0})

Якщо ви хочете змінити значення за замовчуванням, перезапишіть default_factory:

>>> d_int.default_factory = lambda: 1
>>> d_int['e']
1
>>> d_int
defaultdict(<function <lambda> at 0x7f34a0a91578>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0})

або

>>> def foo():
...     return 2
>>> d_int.default_factory = foo
>>> d_int['f']
2
>>> d_int
defaultdict(<function foo at 0x7f34a0a0a140>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0, 'f': 2})

Приклади у питанні

Приклад 1

Оскільки int було передано як default_factory, будь-який невідомий ключ поверне 0 за замовчуванням.

Тепер, коли рядок передається у циклі, це збільшить кількість цих алфавітів у d.

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

Приклад 2

Оскільки список був переданий як default_factory, будь-яка невідома (неіснуюча) клавіша поверне [] (тобто список) за замовчуванням.

Тепер, коли список кортежів передається у циклі, він додасть значення у d [color]

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

20

Словники - це зручний спосіб зберігання даних для подальшого пошуку по імені (клавіші). Клавіші повинні бути унікальними, незмінними об'єктами і, як правило, є рядками. Значення у словнику можуть бути будь-якими. Для багатьох застосувань значення є простими типами, такими як цілі числа та рядки.

Це стає більш цікавим, коли значення у словнику є колекціями (списками, диктами тощо). У цьому випадку значення (порожній список або диктант) має бути ініціалізовано при першому використанні заданого ключа. Хоча це зробити досить просто вручну, тип за замовчуванням автоматизує та спрощує такі операції. Вирок за замовчуванням працює точно як звичайний дикт, але він ініціалізується функцією ("заводська фабрика"), яка не бере аргументів і надає значення за замовчуванням для неіснуючого ключа.

Вирок за замовчуванням ніколи не викликає 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

Ось ще один приклад того, як за допомогою типового рішення ми можемо зменшити складність

from collections import defaultdict
# Time complexity O(n^2)
def delete_nth_naive(array, n):
    ans = []
    for num in array:
        if ans.count(num) < n:
            ans.append(num)
    return ans

# Time Complexity O(n), using hash tables.
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))

На закінчення, коли вам потрібен словник, і значення кожного елемента має починатися зі значення за замовчуванням, використовуйте вирок за замовчуванням.


18

Тут є чудове пояснення defaultdicts: http://ludovf.net/blog/python-collections-defaultdict/

В основному параметри int та list - це функції, які ви передаєте. Пам'ятайте, що Python приймає назви функцій як аргументи. int повертає 0 за замовчуванням, а list повертає порожній список при виклику з дужками.

У звичайних словниках, якщо у вашому прикладі я спробую зателефонувати d[a], я отримаю помилку (KeyError), оскільки існують лише клавіші m, s, i і p і ключ a не ініціалізований. Але у засудженні, він приймає ім'я функції як аргумент, коли ви намагаєтесь використовувати ключ, який не був ініціалізований, він просто викликає функцію, яку ви передали, і призначає її повернене значення як значення нового ключа.


7

Оскільки питання стосується "як це працює", деякі читачі, можливо, захочуть побачити більше гайок і болтів. Зокрема, йдеться про __missing__(key)метод , про який йде мова . Дивіться: https://docs.python.org/2/library/collections.html#defaultdict-objects .

Більш конкретно, ця відповідь показує, як використати __missing__(key)практичний спосіб: https://stackoverflow.com/a/17956989/1593924

Щоб уточнити, що означає "дзвінок", ось інтерактивний сеанс (з 2.7.6, але повинен працювати і в v3):

>>> 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})

Це було найбільш типовим використанням засудження за замовчуванням (за винятком безглуздого використання змінної 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})
>>> 

7

Мій власний 2 ¢: ви також можете підкласи за замовчуванням:

class MyDict(defaultdict):
    def __missing__(self, key):
        value = [None, None]
        self[key] = value
        return value

Це може стати в нагоді для дуже складних випадків.


4

Поведінку defaultdictможна легко імітувати, використовуючи dict.setdefaultзамість d[key]кожного дзвінка.

Іншими словами, код:

from collections import defaultdict

d = defaultdict(list)

print(d['key'])                        # empty list []
d['key'].append(1)                     # adding constant 1 to the list
print(d['key'])                        # list containing the constant [1]

еквівалентно:

d = dict()

print(d.setdefault('key', list()))     # empty list []
d.setdefault('key', list()).append(1)  # adding constant 1 to the list
print(d.setdefault('key', list()))     # list containing the constant [1]

Єдина відмінність полягає в тому, що, використовуючи defaultdictконструктор списку, викликається лише один раз, а використання dict.setdefaultконструктора списку викликається частіше (але код може бути переписаний, щоб уникнути цього, якщо це дійсно потрібно).

Дехто може стверджувати, що є врахування ефективності, але ця тема є мінним полем. Ця публікація показує, що, наприклад, не існує великого підвищення продуктивності використання стандартного рішення.

IMO, defaultdict - це збірник, який додає коду більше плутанини, ніж користі. Мені марно, але інші можуть думати інакше.


3

Засіб за замовчуванням - це контейнер у класі колекцій Python. Він схожий на звичайний контейнер словника (dict), але має одну відмінність: тип даних полів значень задається після ініціалізації.

Наприклад:

from collections import defaultdict

d = defaultdict(list)

d['python'].append("awesome")

d['something-else'].append("not relevant")

d['python'].append("language")

for i in d.items():

    print i

Це відбитки:

('python', ['awesome', 'language'])
('something-else', ['not relevant'])

"Тип даних полів значень задається після ініціалізації": це неправильно. Надається фабрична функція. Ось listфункція викликати заповнення пропущеного значення, а не тип об’єктів для створення. Наприклад, щоб мати значення за замовчуванням 1, ви б використовували lambda:1це, очевидно, не тип.
asac

2

Я думаю, що найкраще використовувати замість заяви про вимикач. Уявіть, чи є у нас випадок випадку перемикання, як показано нижче:

option = 1

switch(option) {
    case 1: print '1st option'
    case 2: print '2nd option'
    case 3: print '3rd option'
    default: return 'No such option'
}

У switchpython не доступні заяви про випадки. Ми можемо досягти того ж, використовуючи defaultdict.

from collections import defaultdict

def default_value(): return "Default Value"
dd = defaultdict(default_value)

dd[1] = '1st option'
dd[2] = '2nd option'
dd[3] = '3rd option'

print(dd[4])    
print(dd[5])    
print(dd[3])

Він друкує:

Default Value
Default Value
3rd option

У вищевказаному фрагменті ddнемає клавіш 4 або 5, а значить, він виводить значення за замовчуванням, яке ми налаштували в хелперній функції. Це досить приємно, ніж словник-сирий, де KeyErrorвикидається a , якщо ключа немає. З цього видно, що defaultdictбільше схоже на заяву випадку переключення, де ми можемо уникнути складних if-elif-elif-elseблоків.

Ще один хороший приклад, який мене дуже вразив на цьому сайті :

>>> from collections import defaultdict
>>> food_list = 'spam spam spam spam spam spam eggs spam'.split()
>>> food_count = defaultdict(int) # default value of int is 0
>>> for food in food_list:
...     food_count[food] += 1 # increment element's value by 1
...
defaultdict(<type 'int'>, {'eggs': 1, 'spam': 7})
>>>

Якщо спробувати отримати доступ до інших , ніж елементи , eggsі spamми отримаємо відлік від 0.


2

Без цього defaultdict, можливо, ви можете призначити нові значення невидимим ключам, але ви не можете їх змінити. Наприклад:

import collections
d = collections.defaultdict(int)
for i in range(10):
  d[i] += i
print(d)
# Output: defaultdict(<class 'int'>, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9})

import collections
d = {}
for i in range(10):
  d[i] += i
print(d)
# Output: Traceback (most recent call last): File "python", line 4, in <module> KeyError: 0

2

Добре, що за замовчуванням може призвести також помилка keyerror у наступному випадку:

    from collections import defaultdict
    d = defaultdict()
    print(d[3]) #raises keyerror

Завжди не забудьте аргументувати вирок за замовчуванням, як, наприклад, default (int).


0

Стандартний словник включає метод setdefault () для отримання значення та встановлення за замовчуванням, якщо значення не існує. На противагу цьому, типовий параметр дозволяє абоненту вказати типовий параметр за замовчуванням при ініціалізації контейнера.

import collections

def default_factory():
    return 'default value'

d = collections.defaultdict(default_factory, foo='bar')
print 'd:', d
print 'foo =>', d['foo']
print 'bar =>', d['bar']

Це працює добре до тих пір, поки для всіх клавіш доцільно мати однакові за замовчуванням. Це може бути особливо корисним, якщо типовим є тип, який використовується для агрегування або накопичення значень, наприклад, список, набір або навіть int. Стандартна бібліотечна документація включає декілька прикладів використання цього рішення за замовчуванням.

$ python collections_defaultdict.py

d: defaultdict(<function default_factory at 0x100468c80>, {'foo': 'bar'})
foo => bar
bar => default value

0

Коротко:

defaultdict(int) - аргумент int вказує, що значення будуть тип int.

defaultdict(list) - список аргументів вказує, що значення будуть типом списку.


-9

Документація та пояснення в значній мірі самі собою пояснюють:

http://docs.python.org/library/collections.html#collections.defaultdict

Функція типу (int / str тощо), передана як аргумент, використовується для ініціалізації значення за замовчуванням для будь-якого даного ключа, де ключ не присутній у диктаті.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.