2010-04-08 34 views
116

Dzięki kilku wspaniałym osobom z SO, odkryłem możliwości oferowane przez collections.defaultdict, szczególnie w zakresie czytelności i szybkości. Użyłem ich z powodzeniem.Wiele poziomów "collection.defaultdict" w Pythonie

Teraz chciałbym wprowadzić trzy poziomy słowników, z których dwa to: defaultdict, a najniższy to int. Nie znajduję odpowiedniego sposobu na zrobienie tego. Oto moja próba:

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] 

Teraz to działa, ale dodaje się, co jest pożądane zachowanie, nie:

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

Podejrzewam, że powinienem gdzieś ogłosił, że drugi poziom defaultdict jest typu int, ale nie znalazłem gdzie i jak to zrobić.

Powodem, dla którego używam defaultdict w pierwszej kolejności, jest unikanie konieczności inicjowania słownika dla każdego nowego klucza.

Jakaś bardziej elegancka propozycja?

Dzięki pythonom!

Odpowiedz

244

Zastosowanie:

d = defaultdict(lambda: defaultdict(int)) 

Spowoduje to utworzenie nowego defaultdict(int) gdy nowy klucz jest dostępny w d.

+0

Jedynym problemem jest to, że nie będzie się ono rozpadać, co oznacza, że ​​"przetwarzanie wieloprocesowe" jest niezadowolone z wysyłania tych plików do iz powrotem. – Noah

+15

@Noah: Zachwyci się, jeśli użyje się nazwanej funkcji poziomu modułu zamiast lambda. – interjay

+0

oczywiście, głupio mnie. – Noah

10

Popatrz na odpowiedź nosklo here, aby uzyskać bardziej ogólne rozwiązanie.

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 

Testing:

a = AutoVivification() 

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

print a 

Output:

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

Dzięki za link @ miles82 (i edycji, @voyager). Jak pythonowskie i bezpieczne jest takie podejście? – Morlock

+0

Niestety to rozwiązanie nie zachowuje najbardziej poręcznej części defaultdict, która jest zdolnością napisania czegoś takiego jak D ['klucz'] + = 1 bez martwienia się o istnienie klucza. To jest główna cecha, której używam defaultdict dla ... ale mogę sobie wyobrazić, że dynamicznie pogłębiające się słowniki są całkiem przydatne. – rschwieb

+1

@rschwieb możesz dodać moc do zapisu + = 1, dodając metodę __add__. – spazm

3

Jak na zamówienie @ rschwieb za D['key'] += 1 możemy rozwinąć previous nadrzędnymi dodatek definiując __add__ sposób, aby ten zachowywał się bardziej jak collections.Counter()

Pierwszy __missing__ zostanie wezwany do utworzyć nowy pusty wartość, która zostanie przekazana do __add__. Testujemy wartość, licząc na puste wartości: False.

Aby uzyskać więcej informacji na temat przesłonięcia, zobacz temat .

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 

Przykłady:

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

Zamiast sprawdzania argument jest liczbą (bardzo non-python, amirite!) Możemy tylko zapewnić domyślną wartość 0, a następnie próbować operacji:

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 
+0

powinny one podnieść NotImplemented zamiast ValueError? – spazm

13

Innym sposobem dokonania pickleable, zagnieżdżone defaultdict jest użycie częściowy obiekt zamiast lambda:

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

ten będzie działać, ponieważ klasa defaultdict jest globalnie dostępna na poziomie modułu:

"You can't pickle a partial object unless the function [or in this case, class] it wraps is globally accessible ... under its __name__ (within its __module__)" -- Pickling wrapped partial functions

Powiązane problemy