Anahtarı defaultdict'in default_factory'sine geçirmenin akıllıca bir yolu var mı?


95

Bir sınıf, bir parametre alan bir kurucuya sahiptir:

class C(object):
    def __init__(self, v):
        self.v = v
        ...

Kodun bir yerinde, bir diktedeki değerlerin anahtarlarını bilmeleri yararlıdır.
Yeni doğan varsayılan değerlerine geçirilen anahtarla bir defaultdict kullanmak istiyorum:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

Herhangi bir öneri?

Yanıtlar:


128

Pek olarak nitelendirir zeki - ama sınıflara sizin arkadaşınız:

class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError( key )
        else:
            ret = self[key] = self.default_factory(key)
            return ret

d = keydefaultdict(C)
d[x] # returns C(x)

16
Kaçınmaya çalıştığım tam da bu çirkinlik ... Basit bir söz kullanmak ve anahtarın varlığını kontrol etmek bile çok daha temiz.
Benjamin Nitlehoo

1
@Paul: ve yine de cevabın bu. Çirkinlik? Haydi!
tzot

4
Sanırım bu kod parçasını alıp kişiselleştirilmiş genel araçlar modülüme koyacağım, böylece istediğim zaman kullanabilirim. Bu şekilde çok çirkin değil ...
weronika

24
+1 Doğrudan OP'nin sorusuna hitap ediyor ve bana "çirkin" görünmüyor. Ayrıca iyi bir cevap birçok nedeni olduğunu fark görünmüyor defaultdict'ın __missing__()(built-in herhangi alt sınıfta olabildiğince yöntem geçersiz kılınan olabilir dictsınıfının sürüm 2.5 beri).
martineau

7
+1 __missing__ işlevinin tüm amacı, eksik tuşlar için davranışı özelleştirmektir. @Silentghost tarafından bahsedilen dict.setdefault () yaklaşımı da işe yarar (artı tarafta, setdefault () kısadır ve zaten mevcuttur; eksi tarafta, verimlilik sorunlarından muzdariptir ve hiç kimse "setdefault" adını gerçekten sevmez) .
Raymond Hettinger

26

Hayır yok.

defaultdictUygulama eksik geçmek yapılandırılamaz keyiçin default_factoryout-of-the-box. Tek seçeneğiniz defaultdictyukarıda @JochenRitzel tarafından önerildiği gibi kendi alt sınıfınızı uygulamaktır .

Ancak bu "akıllı" değildir veya standart bir kitaplık çözümü (eğer varsa) kadar temiz değildir. Dolayısıyla özlü, evet / hayır sorunuzun cevabı açıkça "Hayır" dır.

Standart kitaplıkta bu kadar sık ​​ihtiyaç duyulan bir aracın eksik olması çok kötü.


Evet, fabrikanın anahtarı almasına izin vermek daha iyi bir tasarım seçimi olurdu (sıfır yerine tekli işlev). Bir sabiti döndürmek istediğimizde bir argümanı atmak kolaydır.
Yvesgere,

6

Buraya hiç ihtiyacın olduğunu sanmıyorum defaultdict. Neden sadece dict.setdefaultyöntemi kullanmıyorsunuz ?

>>> d = {}
>>> d.setdefault('p', C('p')).v
'p'

Bu elbette birçok örneğini yaratacaktır C. Bir sorun olması durumunda, daha basit yaklaşımın işe yarayacağını düşünüyorum:

>>> d = {}
>>> if 'e' not in d: d['e'] = C('e')

Görebildiğim defaultdictkadarıyla , ya da diğer alternatiflerden daha hızlı olurdu .

inTestin hızına karşı try-exclude maddesinin kullanılmasıyla ilgili ETA :

>>> def g():
    d = {}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(g)
0.19638929363557622
>>> def f():
    d = {}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(f)
0.6167065411074759
>>> def k():
    d = {'a': 2}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(k)
0.30074866358404506
>>> def p():
    d = {'a': 2}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(p)
0.28588609450770264

7
Bu, d'ye birçok kez erişildiği ve bir anahtarı nadiren kaçırdığı durumlarda oldukça israf edicidir: C (anahtar), böylece GC'nin toplaması için tonlarca gereksiz nesne yaratacaktır. Ayrıca, benim durumumda ek bir acı var, çünkü yeni C nesneleri oluşturmak yavaş.
Benjamin Nitlehoo

@Paul: bu doğru. O zaman daha da basit bir yöntem öneririm, düzenlememe bakın.
SilentGhost

Varsayılantan daha hızlı olduğundan emin değilim, ancak genellikle yaptığım şey bu (THC4k'nin cevabına yaptığım açıklamaya bakın). Varsayılan_factory'nin argüman içermediği gerçeğini kırmanın, kodu biraz daha zarif tutmanın basit bir yolu olduğunu umdum.
Benjamin Nitlehoo

5
@SilentGhost: Anlamıyorum - bu OP'nin problemini nasıl çözüyor? OP'nin eğer d[key]geri dönmek için herhangi bir okuma girişimi istediğini düşündüm . Ama çözümünüz onun gerçekten gitmesini ve önceden ayarlamasını gerektiriyor mu? Hangisine ihtiyacı olduğunu nasıl bilebilirdi ? d[key] = C(key)key not in dd[key]key
en fazla

2
Çünkü setdefault cehennem kadar çirkin olduğundan ve koleksiyondaki varsayılan diktenin anahtarı alan bir fabrika işlevini desteklemesi GEREKİR. Python tasarımcılarından ne kadar boşa harcanan bir fırsat!
jgomo3

0

Otomatik olarak değer ekleyen bir sözlüğün çalışan bir örneğini burada bulabilirsiniz. / Usr / include içinde yinelenen dosyaları bulmada gösteri görevi. PathDict özelleştirme sözlüğünün yalnızca dört satır gerektirdiğini unutmayın :

class FullPaths:

    def __init__(self,filename):
        self.filename = filename
        self.paths = set()

    def record_path(self,path):
        self.paths.add(path)

class PathDict(dict):

    def __missing__(self, key):
        ret = self[key] = FullPaths(key)
        return ret

if __name__ == "__main__":
    pathdict = PathDict()
    for root, _, files in os.walk('/usr/include'):
        for f in files:
            path = os.path.join(root,f)
            pathdict[f].record_path(path)
    for fullpath in pathdict.values():
        if len(fullpath.paths) > 1:
            print("{} located in {}".format(fullpath.filename,','.join(fullpath.paths)))
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.