2013-07-08 9 views
13

Jedną z mniejszych irytacji z dict.setdefault jest to, że zawsze ocenia swój drugi argument (oczywiście podany), nawet jeśli pierwszy pierwszy argument jest już kluczem w słowniku.Jak zaimplementować leniwy setdefault?

Na przykład:

import random 
def noisy_default(): 
    ret = random.randint(0, 10000000) 
    print 'noisy_default: returning %d' % ret 
    return ret 

d = dict() 
print d.setdefault(1, noisy_default()) 
print d.setdefault(1, noisy_default()) 

ta produkuje ouptut jak następuje:

noisy_default: returning 4063267 
4063267 
noisy_default: returning 628989 
4063267 

Jako ostatnia linia potwierdza, drugie wykonanie noisy_default jest niepotrzebny, ponieważ od tego momentu kluczem 1 jest już obecne w d (o wartości 4063267).

Czy jest możliwe zaimplementowanie podklasy dict, której metoda setdefault ocenia leniwie swój drugi argument?


EDIT:

Poniżej jest implementacja zainspirowany komentarzem BrenBarn i odpowiedzi Pavel Anossov użytkownika. W tym czasie zastosowałem leniwą wersję geta, ponieważ idea jest zasadniczo taka sama.

class LazyDict(dict): 
    def get(self, key, thunk=None): 
     return (self[key] if key in self else 
       thunk() if callable(thunk) else 
       thunk) 


    def setdefault(self, key, thunk=None): 
     return (self[key] if key in self else 
       dict.setdefault(self, key, 
           thunk() if callable(thunk) else 
           thunk)) 

Teraz fragment

d = LazyDict() 
print d.setdefault(1, noisy_default) 
print d.setdefault(1, noisy_default) 

produkuje wyjście tak:

noisy_default: returning 5025427 
5025427 
5025427 

Zauważ, że drugi argument d.setdefault powyżej obecnie jest wymagalne, a nie wywołanie funkcji.

Gdy drugi argument do LazyDict.get lub LazyDict.setdefault nie jest wywoływalny, zachowują się one tak samo, jak odpowiadające im metody dict.

Jeśli ktoś chce przejść wywoływalnym jako wartość domyślną samego (tj nie rozumie się nazywa), lub jeżeli wpłacone na miano wymaga argumentów, prepend lambda: do odpowiedniej argumentacji. Np:

d1.setdefault('div', lambda: div_callback) 

d2.setdefault('foo', lambda: bar('frobozz')) 

Ci, którzy nie lubią ideę nadrzędnymi get i setdefault i/lub powstałego konieczności badania dla callability, itd., Mogą korzystać z tej wersji Zamiast:

class LazyButHonestDict(dict): 
    def lazyget(self, key, thunk=lambda: None): 
     return self[key] if key in self else thunk() 


    def lazysetdefault(self, key, thunk=lambda: None): 
     return (self[key] if key in self else 
       self.setdefault(key, thunk())) 
+0

Nie można zrobić, aby nie oceniać drugiego argumentu. To, co musisz zrobić, to zawrzeć ten argument w funkcji (np. Z 'lambda'), a następnie ustawić' setdefault' wywoływanie funkcji tylko w razie potrzeby. – BrenBarn

+0

Czy mogę zaproponować dodanie '* args, ** kwargs' do sygnatur' lazyget', 'lazysetdefault' i wywołanie' thunk() '? To pozwoli twoim leniwym rzeczom na przyjmowanie parametrów. na przykład 'lbd.lazysetdefault ('total', sum, [1, 2, 3, 4], start = 2)' – Hounshell

Odpowiedz

6

Nie, ocena argumentów ma miejsce przed wywołaniem. Możesz zaimplementować funkcję podobną do setdefault, która pobiera argument jako drugi argument i wywołuje ją tylko wtedy, gdy jest potrzebna.

9

Można to również osiągnąć za pomocą defaultdict. Jest tworzony z wywoływaniem, które jest wywoływane, gdy dostęp do nieistniejącego elementu jest możliwy.

from collections import defaultdict 

d = defaultdict(noisy_default) 
d[1] # noise 
d[1] # no noise 

Zastrzeżenie z defaultdict jest to, że wpłacone pobiera żadnych argumentów, więc nie można wyprowadzić wartość domyślną z klucza jak mogłeś z dict.setdefault. Może to być złagodzone poprzez nadpisanie __missing__ w podklasie:

from collections import defaultdict 

class defaultdict2(defaultdict): 
    def __missing__(self, key): 
     value = self.default_factory(key) 
     self[key] = value 
     return value 

def noisy_default_with_key(key): 
    print key 
    return key + 1 

d = defaultdict2(noisy_default_with_key) 
d[1] # prints 1, sets 2, returns 2 
d[1] # does not print anything, does not set anything, returns 2 

Aby uzyskać więcej informacji, zajrzyj do modułu collections.

4

Można to zrobić w jednej liniowej używając potrójny operatora:

value = cache[key] if key in cache else cache.setdefault(key, func(key)) 

Jeżeli jesteś pewien, że cache nigdy nie przechowywać wartości falsy można uprościć trochę:

value = cache.get(key) or cache.setdefault(key, func(key)) 
+1

Jeśli sprawdzasz 'key in dict', nie ma sensu używać' setdeault' – user1685095

+1

To będzie wymagać Wyszukaj 'key' w' cache' dwa razy. To nie jest wielka sprawa dla dyktatu opartego na Hash-Map, ale wciąż nie ma sensu. –

+0

@ user1685095 Jeśli nie wywołasz setdefault, pamięć podręczna nie zostanie zaktualizowana. setdefault ustawia jednocześnie pustą pamięć podręczną i zwraca jej wartość w tym samym czasie –