Czy istnieje sprytny sposób na przekazanie klucza do fabryki defaultdict?


95

Klasa posiada konstruktor, który przyjmuje jeden parametr:

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

Gdzieś w kodzie przydatne jest, aby wartości w dyktandzie znały swoje klucze.
Chcę użyć defaultdict z kluczem przekazanym do wartości domyślnych noworodka:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

Jakieś sugestie?

Odpowiedzi:


128

Trudno kwalifikować się jako sprytne - ale podklasy są twoim przyjacielem:

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
To jest dokładnie ta brzydota, której staram się uniknąć ... Nawet używając prostego dyktowania i sprawdzanie istnienia klucza jest znacznie czystsze.
— Benjamin Nitlehoo

1
@Paul: a jednak to jest twoja odpowiedź. Brzydota? Daj spokój!
— tzot

4
Myślę, że po prostu wezmę ten fragment kodu i umieszczę go w moim spersonalizowanym module narzędzi ogólnych, aby móc go używać, kiedy tylko zechcę. Nie tak brzydko ...
— weronika

24
+1 Bezpośrednio odpowiada na pytanie OP i nie wygląda dla mnie „brzydko”. Również dobre rozwiązanie, ponieważ wielu z nich nie zdawał sobie sprawy, że defaultdictjest __missing__()metoda może być pominięte (jak to można w dowolnej podklasy wbudowanych w dictklasie od wersji 2.5).
— martineau

7
+1 Ogólnym celem __braknięcia__ jest dostosowanie zachowania brakujących kluczy. Podejście dict.setdefault () wspomniane przez @silentghost również by działało (na plus, setdefault () jest krótka i już istnieje; z drugiej strony ma problemy z wydajnością i nikt nie lubi nazwy "setdefault") .
— Raymond Hettinger

26

Nie, nie ma.

defaultdictRealizacja nie może być skonfigurowany do przekazywania brakuje keydo default_factoryout-of-the-box. Jedyną opcją jest zaimplementowanie własnej defaultdictpodklasy, zgodnie z sugestią @JochenRitzel powyżej.

Ale to nie jest „sprytne” ani prawie tak czyste, jak byłoby to standardowe rozwiązanie biblioteczne (gdyby istniało). Zatem odpowiedź na twoje zwięzłe pytanie, tak / nie, brzmi wyraźnie „nie”.

Szkoda, że ​​w standardowej bibliotece brakuje tak często potrzebnego narzędzia.


Tak, byłoby lepszym wyborem projektowym, gdyby fabryka wzięła klucz (funkcja jednoargumentowa zamiast pustej). Łatwo jest odrzucić argument, gdy chcemy zwrócić stałą.
— YvesgereY

6

Myślę, że wcale nie potrzebujesz defaultdicttutaj. Dlaczego po prostu nie użyć dict.setdefaultmetody?

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

To oczywiście spowodowałoby wiele przypadków C. Myślę, że w przypadku problemu wystarczy prostsze podejście:

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

Byłoby to szybsze niż ta defaultdictlub jakakolwiek inna alternatywa, o ile widzę.

ETA w odniesieniu do szybkości intestu vs. stosowanie klauzuli try-except:

>>> 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
Jest to wysoce marnotrawne w przypadkach, gdy d jest używany wiele razy i rzadko brakuje klucza: C (klucz) stworzy w ten sposób tony niepotrzebnych obiektów do zebrania przez GC. Ponadto w moim przypadku pojawia się dodatkowy ból, ponieważ tworzenie nowych obiektów C jest powolne.
— Benjamin Nitlehoo

@Paul: zgadza się. Proponuję wtedy jeszcze prostszą metodę, zobacz moją edycję.
— SilentGhost

Nie jestem pewien, czy jest szybszy niż defaultdict, ale tak właśnie robię (zobacz mój komentarz do odpowiedzi THC4k). Miałem nadzieję, że istnieje prosty sposób na obejście faktu, że default_factory nie przyjmuje żadnych argumentów, aby kod był nieco bardziej elegancki.
— Benjamin Nitlehoo

5
@SilentGhost: Nie rozumiem - jak to rozwiązuje problem OP? Myślałem, że OP chciał jakiejkolwiek próby czytania, d[key]aby wrócić, d[key] = C(key)jeśli key not in d. Ale twoje rozwiązanie wymaga od niego, aby faktycznie działał i ustawiał d[key]z wyprzedzeniem? Skąd miałby wiedzieć, czego keybędzie potrzebował?
— maks.

2
Ponieważ setdefault jest brzydki jak diabli, a defaultdict z kolekcji POWINIEN obsługiwać funkcję fabryczną, która otrzyma klucz. Cóż za zmarnowana szansa ze strony projektantów Pythona!
— jgomo3

0

Oto działający przykład słownika, który automatycznie dodaje wartość. Zadanie demonstracyjne polegające na znajdowaniu zduplikowanych plików w / usr / include. Zauważ, że słownik PathDict dostosowywania wymaga tylko czterech wierszy:

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)))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.