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.