Python'da birden fazla 'collection.defaultdict' seviyesi


176

SO'daki bazı harika insanlar sayesinde collections.defaultdict, özellikle okunabilirlik ve hızda sunulan olanakları keşfettim . Onları başarı ile kullanmaya başladım.

Şimdi, iki üst düzey defaultdictve en düşük olan olmak üzere üç düzey sözlük uygulamak istiyorum int. Bunu yapmanın uygun yolunu bulamıyorum. İşte benim girişimim:

from collections import defaultdict
d = defaultdict(defaultdict)
a = [("key1", {"a1":22, "a2":33}),
     ("key2", {"a1":32, "a2":55}),
     ("key3", {"a1":43, "a2":44})]
for i in a:
    d[i[0]] = i[1]

Şimdi bu çalışıyor, ancak istenen davranış olan aşağıdakiler çalışmıyor:

d["key4"]["a1"] + 1

Bir yerde ikinci seviyenin defaultdicttür olduğunu ilan etmem gerektiğinden şüpheliyim int, ama nerede veya nasıl yapacağımı bulamadım.

defaultdictİlk başta kullanmamın nedeni , her yeni anahtar için sözlüğü başlatmak zorunda kalmamaktır.

Daha zarif bir öneriniz var mı?

Teşekkürler pythoneers!

Yanıtlar:


341

kullanın:

from collections import defaultdict
d = defaultdict(lambda: defaultdict(int))

Bu, defaultdict(int)yeni bir anahtara her erişildiğinde yeni bir dosya oluşturur d.


2
Tek sorun turşu olmayacak, yani multiprocessingbunları ileri geri göndermekten mutsuz.
Noah

19
@Noah: Lambda yerine adlandırılmış modül düzeyinde bir işlev kullanırsanız turşu olur.
interjay

4
@ScienceFriction Yardıma ihtiyacınız olan belirli bir şey var mı? Ne zaman d[new_key]erişildiğinde, yeni yaratacak lambda arayacak defaultdict(int). Ve ne zaman d[existing_key][new_key2]erişilirse, yeni bir intyaratılacak.
interjay

11
Bu harika. Görünüşe göre evlilik python'umu günlük olarak Python'a yenileyiyorum.
mVChr

3
Bu yöntemi kullanma multiprocessingve adlandırılmış modül düzeyinde işlev hakkında daha fazla ayrıntı mı arıyorsunuz? Bu soru bunu takip ediyor.
Cecilia

32

Seçilebilir, iç içe varsayılan bir karar vermenin başka bir yolu, lambda yerine kısmi bir nesne kullanmaktır:

from functools import partial
...
d = defaultdict(partial(defaultdict, int))

Defaultdict sınıfına modül düzeyinde genel olarak erişilebildiğinden bu işe yarar:

"Sarıldığı işleve (veya bu durumda, sınıfa) işlev genel olarak erişilemezse kısmi bir nesne seçemezsiniz ... __name__ altında (__module__ içinde)" - Sarma kısmi işlevler


12

Nosklo cevabı bak burada daha genel bir çözüm için.

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

Test yapmak:

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

Çıktı:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}

@ Miles82 (ve @voyager düzenlemesi) bağlantısı için teşekkürler. Bu yaklaşım ne kadar güvenli ve güvenlidir?
Morlock

2
Ne yazık ki bu çözüm varsayılan anahtarın en kullanışlı kısmını korumamaktadır, bu da anahtarın varlığından endişe etmeden D ['anahtar'] + = 1 gibi bir şey yazma gücüdür. Bu defaultdict için kullandığım ana özellik ... ama dinamik olarak derinleşen sözlüklerin de oldukça kullanışlı olduğunu hayal edebiliyorum.
rschwieb

2
@rschwieb ekleme yöntemi ekleyerek + = 1 yazma gücünü ekleyebilirsiniz .
spazm

5

@ Rschwieb'in isteğine göre D['key'] += 1, yöntemi tanımlayarak ekleme işlemini geçersiz kılarak öncekine genişletebiliriz.__add__collections.Counter()

İlk __missing__olarak, aktarılacak yeni bir boş değer oluşturmak için çağrılacaktır __add__. Değeri test ediyoruz, boş değerlere güvenerekFalse .

Geçersiz kılma hakkında daha fazla bilgi için sayısal türlere öykünme konusuna bakın .

from numbers import Number


class autovivify(dict):
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

    def __add__(self, x):
        """ override addition for numeric types when self is empty """
        if not self and isinstance(x, Number):
            return x
        raise ValueError

    def __sub__(self, x):
        if not self and isinstance(x, Number):
            return -1 * x
        raise ValueError

Örnekler:

>>> import autovivify
>>> a = autovivify.autovivify()
>>> a
{}
>>> a[2]
{}
>>> a
{2: {}}
>>> a[4] += 1
>>> a[5][3][2] -= 1
>>> a
{2: {}, 4: 1, 5: {3: {2: -1}}}

Argümanı kontrol etmek yerine bir Sayı (çok python olmayan, amirit!) Yerine, varsayılan bir 0 değeri sağlayabilir ve işlemi deneyebiliriz:

class av2(dict):
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

    def __add__(self, x):
        """ override addition when self is empty """
        if not self:
            return 0 + x
        raise ValueError

    def __sub__(self, x):
        """ override subtraction when self is empty """
        if not self:
            return 0 - x
        raise ValueError

bunlar ValueError yerine NotImplemented'i yükseltmeli mi?
spazm

5

Partiye geç, ama keyfi derinlik için kendimi böyle bir şey yaparken buldum:

from collections import defaultdict

class DeepDict(defaultdict):
    def __call__(self):
        return DeepDict(self.default_factory)

Buradaki hile, temelde DeepDictörneğin kendisini eksik değerler oluşturmak için geçerli bir fabrika haline getirmektir . Şimdi böyle şeyler yapabiliriz

dd = DeepDict(DeepDict(list))
dd[1][2].extend([3,4])
sum(dd[1][2])  # 7

ddd = DeepDict(DeepDict(DeepDict(list)))
ddd[1][2][3].extend([4,5])
sum(ddd[1][2][3])  # 9

1
def _sub_getitem(self, k):
    try:
        # sub.__class__.__bases__[0]
        real_val = self.__class__.mro()[-2].__getitem__(self, k)
        val = '' if real_val is None else real_val
    except Exception:
        val = ''
        real_val = None
    # isinstance(Avoid,dict)也是true,会一直递归死
    if type(val) in (dict, list, str, tuple):
        val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
        # 重新赋值当前字典键为返回值,当对其赋值时可回溯
        if all([real_val is not None, isinstance(self, (dict, list)), type(k) is not slice]):
            self[k] = val
    return val


def _sub_pop(self, k=-1):
    try:
        val = self.__class__.mro()[-2].pop(self, k)
        val = '' if val is None else val
    except Exception:
        val = ''
    if type(val) in (dict, list, str, tuple):
        val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
    return val


class DefaultDict(dict):
    def __getitem__(self, k):
        return _sub_getitem(self, k)

    def pop(self, k):
        return _sub_pop(self, k)

In[8]: d=DefaultDict()
In[9]: d['a']['b']['c']['d']
Out[9]: ''
In[10]: d['a']="ggggggg"
In[11]: d['a']
Out[11]: 'ggggggg'
In[12]: d['a']['pp']
Out[12]: ''

Yine hata yok. Kaç seviye iç içe olursa olsun. hata da yok

dd = DefaultDict ({ "1": 333333})

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.