Collections.defaultdict nasıl çalışır?


532

Python belgelerindeki örnekleri okudum, ancak yine de bu yöntemin ne anlama geldiğini anlayamıyorum. Birisi yardımcı olabilir mi? İşte python belgelerinden iki örnek

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

ve

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

parametreleri intve listne için?


15
BTW, kullanım durumunuza bağlı olarak , defaultdict'i doldurmayı bitirdikten sonra ayarlayarak salt okunur kullanım için defaultdict'i dondurmayı unutmayındefault_factory = None . Bu soruya bakın .
Acumenus

Yanıtlar:


598

Genellikle, KeyErrorşu anda sözlükte olmayan bir anahtarla bir öğe almaya çalışırsanız bir Python sözlüğü atar . Bunun defaultdictaksine, erişmeye çalıştığınız herhangi bir öğeyi oluşturacaktır (elbette henüz mevcut olmadıkları sürece). Böyle bir "varsayılan" öğe oluşturmak için, yapıcıya ilettiğiniz işlev nesnesini çağırır (daha kesin olarak, işlev ve tür nesnelerini içeren isteğe bağlı "çağrılabilir" bir nesnedir). İlk örnekte, int()tamsayı nesnesini döndürecek varsayılan öğeler kullanılarak oluşturulur 0. İkinci örnek için, list()yeni bir boş liste nesnesi döndüren varsayılan öğeler kullanılarak oluşturulur .


4
İşlevsel olarak d.get (key, default_val) kullanmaktan farklı mıdır?
Ambareesh

29
@Ambareesh d.get(key, default)asla sözlüğünüzü değiştirmez - sadece varsayılanı döndürür ve sözlüğü değiştirmeden bırakır. defaultdict, diğer taraftan, henüz yoksa sözlüğe bir anahtar ekler . Bu büyük bir fark; nedenini anlamak için sorudaki örneklere bakın.
Sven Marnach

Her tür için varsayılan değerin ne olduğunu nasıl bilebiliriz? İnt () için 0 ve list () için [] sezgiseldir, ancak daha karmaşık veya kendi kendine tanımlanmış türler de olabilir.
Sean

1
@Sean defaultdict, geçtiğiniz kurucuyu çağırır. Bir tür geçerseniz T, değerler kullanılarak oluşturulur T(). Hiçbir parametre iletilmeden tüm tipler yapılamaz. Böyle bir tür oluşturmak istiyorsanız, bir sarıcı işlevine veya benzeri bir şeye ihtiyacınız vardır functools.partial(T, arg1, arg2).
Sven Marnach

224

defaultdictsözlükte bir anahtar bulunamazsa, KeyErroratılmak yerine yeni bir giriş oluşturulur. Bu yeni girişin türü defaultdict argümanı tarafından verilir.

Örneğin:

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

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

10
"Bu yeni çiftin türü defaultdict argümanı tarafından verilir." Argümanın yalnızca fonksiyonlar değil, herhangi bir çağrılabilir nesne olabileceğini unutmayın . Örneğin foo "bar" döndüren bir işlevse, foo varsayılan dikte argümanı olarak kullanılabilir ve mevcut olmayan bir anahtara erişilirse değeri "bar" olarak ayarlanır.
lf215

13
Veya sadece "bar" ı döndürmek istiyorsanız: somedict = defaultdict (lambda: "bar")
Michael Scott Cuthbert

Dördüncü satır 0tamsayıyı döndürdü , eğer someddict = defaultdict(list)öyleyse döndürür [ ]. 0 varsayılan tam sayı mı? Veya [] varsayılan liste?
Gathide

Ne. 0değişmezdir - CPython gelen tüm değerler -5için 256singletons önbelleğe alınır ancak bu uygulama özgü bir davranıştır - her iki durumda da yeni bir örneği ile her defasında "oluşturulduğu" int()ya list(). Bu şekilde, d[k].append(v)sözlüğü aynı listeye referanslarla doldurmadan çalışabilir, bu da defaultdictneredeyse işe yaramaz hale gelir. Bu davranış defaultdictolsaydı, parametre olarak lambda yerine bir değer alırdı. (Korkunç açıklama için özür dilerim!)
wizzwizz4

93

defaultdict

"Standart sözlük, bir değer almak ve değer yoksa bir varsayılan oluşturmak için setdefault () yöntemini içerir. Aksine, defaultdictarayanın kap başlatıldığında varsayılanı (döndürülecek değeri) belirtmesini sağlar."

tarafından tanımlanan Doug Hellmann içinde Örnek tarafından Python Standart kütüphane

Defaultdict nasıl kullanılır

İçe aktarma varsayılan metni

>>> from collections import defaultdict

Varsayılan imzayı başlat

Geçerek başlat

ilk argümanı olarak çağrılabilir (zorunlu)

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

** ikinci argüman olarak kwargs (isteğe bağlı)

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

veya

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

Nasıl çalışır

Standart sözlüğün alt sınıfı olduğu gibi, aynı işlevleri yerine getirebilir.

Ancak bilinmeyen bir anahtarın geçilmesi durumunda hata yerine varsayılan değeri döndürür. Örneğin:

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

Varsayılan değeri değiştirmek istediğinizde 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})

veya

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

Sorudaki Örnekler

örnek 1

İnt default_factory olarak geçtiği için, bilinmeyen herhangi bir anahtar varsayılan olarak 0 değerini döndürür.

Dize döngü içinde iletilirken, d'deki bu alfabe sayısını artıracaktır.

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

ÖRNEK 2

Bir liste default_factory olarak geçtiği için, bilinmeyen (var olmayan) anahtarlar varsayılan olarak [] (yani liste) döndürür.

Şimdi tuples listesi döngüde geçtiği için, değeri 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

Sözlükler, verileri ada (anahtar) göre daha sonra almak üzere saklamanın kolay bir yoludur. Anahtarlar benzersiz, değişmez nesneler olmalı ve genellikle dizgidir. Sözlükteki değerler herhangi bir şey olabilir. Birçok uygulama için değerler, tamsayılar ve dizeler gibi basit türlerdir.

Bir sözlükteki değerler koleksiyon olduğunda (listeler, diktler, vb.) Daha ilginç hale gelir. Bu durumda, değer (boş bir liste veya dikte) belirli bir anahtar ilk kullanıldığında başlatılmalıdır. Bunu manuel olarak yapmak nispeten kolay olsa da, defaultdict türü bu tür işlemleri otomatik hale getirir ve basitleştirir. Bir defaultdict tam olarak normal bir dikte gibi çalışır, ancak argüman almayan ve var olmayan bir anahtar için varsayılan değer sağlayan bir işlevle (“varsayılan fabrika”) başlatılır.

Bir defaultdict asla bir KeyError oluşturmaz. Mevcut olmayan herhangi bir anahtar, varsayılan fabrika tarafından döndürülen değeri alır.

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

İşte defaultdict kullanarak karmaşıklığı nasıl azaltabiliriz?

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

Sonuç olarak, sözlüğe her ihtiyacınız olduğunda ve her öğenin değeri varsayılan bir değerle başlamalıdırsa, varsayılan bir ifade kullanın.


18

Burada varsayılan kararların büyük bir açıklaması var: http://ludovf.net/blog/python-collections-defaultdict/

Temel olarak, int ve list parametreleri ilettiğiniz işlevlerdir. Python'un işlev adlarını bağımsız değişken olarak kabul ettiğini unutmayın. int varsayılan olarak 0 değerini döndürür ve parantez ile çağrıldığında liste boş bir liste döndürür.

Normal sözlüklerde, örneğinizde aramayı d[a]denersem, bir hata (KeyError) alırım, çünkü sadece m, s, i ve p tuşları var ve a anahtarı başlatılmadı. Ancak varsayılan bir ifadede, işlev adını bağımsız değişken olarak alır, başlatılmamış bir anahtarı kullanmaya çalıştığınızda, yalnızca geçtiğiniz işlevi çağırır ve dönüş değerini yeni anahtarın değeri olarak atar.


7

Soru "nasıl çalıştığı" ile ilgili olduğundan, bazı okuyucular daha fazla somun ve cıvata görmek isteyebilir. Özellikle, söz konusu yöntem __missing__(key)yöntemdir. Bkz . Https://docs.python.org/2/library/collections.html#defaultdict-objects .

Daha somut olarak, bu cevap __missing__(key)pratik bir şekilde nasıl kullanılacağını gösterir : https://stackoverflow.com/a/17956989/1593924

'Callable' ifadesinin ne anlama geldiğini açıklığa kavuşturmak için etkileşimli bir oturum (2.7.6'dan itibaren v3'te de çalışmalıdır):

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

Bu, defaultdict'in en tipik kullanımıdır (x değişkeninin anlamsız kullanımı hariç). Açık varsayılan değerle 0 ile aynı şeyi yapabilirsiniz, ancak basit bir değerle yapamazsınız:

>>> dd2 = defaultdict(0)

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

Bunun yerine, aşağıdakiler basit bir işlevden geçtiği için çalışır (anında argüman almayan ve her zaman 0 döndüren isimsiz bir işlev oluşturur):

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

Ve farklı bir varsayılan değerle:

>>> dd3 = defaultdict(lambda: 1)
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {})
>>> dd3['a']
1
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {'a': 1})
>>> 

7

Kendi 2 ¢: alt varsayılanı da sınıflandırabilirsiniz:

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

Bu, çok karmaşık durumlar için kullanışlı olabilir.


4

Davranışı, her çağrı yerine yerine defaultdictkolayca taklit edilebilir .dict.setdefaultd[key]

Başka bir deyişle, kod:

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]

şuna eşittir:

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]

Tek fark, defaultdictliste yapıcısının sadece bir kez çağrılması ve dict.setdefaultliste yapıcısının kullanılmasının daha sık çağrılmasıdır (ancak gerçekten gerekirse koddan kaçınmak için kod yeniden yazılabilir).

Bazıları bir performans değerlendirmesi olduğunu iddia edebilir, ancak bu konu bir mayın tarlasıdır. Bu yazı, örneğin defaultdict kullanımında büyük bir performans artışı olmadığını gösterir.

IMO, defaultdict, koda faydadan daha fazla karışıklık katan bir koleksiyon. Benim için işe yaramaz, ama diğerleri farklı düşünebilir.


3

Defaultdict aracı, Python'un koleksiyon sınıfındaki bir kaptır. Her zamanki sözlük (dict) kapsayıcısına benzer, ancak bir farkı vardır: Değer alanlarının veri türü başlatma sonrasında belirtilir.

Örneğin:

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

Bu yazdırır:

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

"Değer alanlarının veri türü başlatma sırasında belirtilir": bu doğru değil. Bir eleman fabrika fonksiyonu sağlanmıştır. Oluşturulacak listnesnelerin türünü değil, eksik bir değeri doldurmak için çağrılacak işlev. Örneğin, varsayılan bir değere sahip olmak için açıkçası bir tür olmayan 1kullanırsınız lambda:1.
asac

2

Bence en iyi bir anahtar durum ifadesi yerine kullanılır. Aşağıdaki gibi bir switch case deyimimiz olduğunu düşünün:

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'da kullanılabilir durum bildirimi yok . Bunu kullanarak da başarabiliriz 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])

Yazdırır:

Default Value
Default Value
3rd option

Yukarıdaki kod parçasında dd4 veya 5 tuşu yoktur ve bu nedenle bir yardımcı işlevde yapılandırdığımız varsayılan bir değeri yazdırır. Bu, KeyErroreğer anahtar yoksa a'nın atıldığı ham bir sözlükten oldukça güzel . Bundan, defaultdictkarmaşık if-elif-elif-elsebloklardan kaçınabileceğimiz bir anahtar durum ifadesi gibi daha açıktır .

Bu siteden beni çok etkileyen güzel bir örnek daha :

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

Dışında herhangi bir öğe erişmeye çalışırsanız eggsve spambiz 0 sayımını alacak.


2

Olmadan defaultdict, görünmeyen tuşlara muhtemelen yeni değerler atayabilirsiniz, ancak değiştiremezsiniz. Örneğin:

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

Defaultdict aşağıdaki durumda da keyerror değerini yükseltebilir:

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

Daima defaultdict (defaultdict (int) gibi) argüman vermeyi unutmayın.


0

Standart sözlük, bir değer almak ve değer yoksa bir varsayılan oluşturmak için setdefault () yöntemini içerir. Bunun aksine, defaultdict, kapsayıcı başlatıldığında arayanın varsayılan yukarı ön belirtmesini sağlar.

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']

Bu, tüm tuşların aynı varsayılana sahip olması uygun olduğu sürece iyi çalışır. Varsayılan, liste, küme ve hatta int gibi değerleri toplamak veya toplamak için kullanılan bir türse özellikle yararlı olabilir. Standart kütüphane belgeleri, defaultdict'i bu şekilde kullanmanın birkaç örneğini içerir.

$ python collections_defaultdict.py

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

0

Kısacası:

defaultdict(int) - int bağımsız değişkeni, değerlerin int türü olacağını gösterir.

defaultdict(list) - bağımsız değişken listesi, değerlerin liste türü olacağını gösterir.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.