Przypadki użycia dla metody dict „setdefault”

192

Dodanie collections.defaultdictPythona 2,5 znacznie zmniejsza zapotrzebowanie na dict„s setdefaultmetody. To pytanie dotyczy naszej wspólnej edukacji:

  1. Co jest setdefaultnadal przydatne w Pythonie 2.6 / 2.7?
  2. Jakie popularne przypadki użycia setdefaultzostały zastąpione collections.defaultdict?
Eli Bendersky
źródło
1
Nieco spokrewnione zbyt stackoverflow.com/questions/7423428/…
użytkownik

Odpowiedzi:

208

Można powiedzieć, że defaultdictjest przydatny w przypadku ustawień domyślnych przed wypełnieniem dykta i setdefaultjest przydatny do ustawienia domyślnych podczas lub po wypełnieniu dykta .

Prawdopodobnie najczęstszy przypadek użycia: grupowanie elementów (w nieposortowanych danych, w przeciwnym razie użyj itertools.groupby)

# really verbose
new = {}
for (key, value) in data:
    if key in new:
        new[key].append( value )
    else:
        new[key] = [value]


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # key might exist already
    group.append( value )


# even simpler with defaultdict 
from collections import defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append( value ) # all keys have a default already

Czasami chcesz się upewnić, że określone klucze istnieją po utworzeniu nagrania. defaultdictnie działa w tym przypadku, ponieważ tworzy klucze tylko w przypadku jawnego dostępu. Myślisz, że używasz czegoś HTTP z wieloma nagłówkami - niektóre są opcjonalne, ale chcesz dla nich wartości domyślne:

headers = parse_headers( msg ) # parse the message, get a dict
# now add all the optional headers
for headername, defaultvalue in optional_headers:
    headers.setdefault( headername, defaultvalue )
Jochen Ritzel
źródło
1
Rzeczywiście, ten IMHO jest głównym przypadkiem użycia przez defaultdict. Czy możesz podać przykład tego, co masz na myśli w pierwszym akapicie?
Eli Bendersky,
2
Muhammad Alkarouri: Najpierw skopiuj dykt, a następnie zastąp niektóre elementy. Często też to robię i wydaje mi się, że to właśnie ten idiom najbardziej preferuje setdefault. Z defaultdictdrugiej strony nie działałoby, gdyby nie wszystkie defaultvaluesbyły równe (tzn. Niektóre są, 0a niektóre są []).
Jochen Ritzel,
2
@ YHC4k, tak. Właśnie dlatego użyłem headers = dict(optional_headers). W przypadku, gdy wartości domyślne nie są równe. Wynik końcowy jest taki sam, jakbyś najpierw otrzymał nagłówki HTTP, a następnie ustawił wartości domyślne dla tych, których nie otrzymałeś. I jest całkiem użyteczny, jeśli już go masz optional_headers. Wypróbuj mój 2-etapowy kod i porównaj go z własnym, a zobaczysz, co mam na myśli.
Muhammad Alkarouri,
19
lub po prostu zróbnew.setdefault(key, []).append(value)
fmalina,
2
Dziwne wydaje mi się to, że najlepsza odpowiedź sprowadza się do defaultdictnawet lepszej niż setdefault(więc gdzie jest teraz przypadek użycia?). Także ChainMaplepiej obsługiwać httpprzykład, IMO.
YvesgereY
29

Często używam setdefaultdla słów kluczowych argumentów, takich jak w tej funkcji:

def notify(self, level, *pargs, **kwargs):
    kwargs.setdefault("persist", level >= DANGER)
    self.__defcon.set(level, **kwargs)
    try:
        kwargs.setdefault("name", self.client.player_entity().name)
    except pytibia.PlayerEntityNotFound:
        pass
    return _notify(level, *pargs, **kwargs)

Doskonale nadaje się do poprawiania argumentów w opakowaniach wokół funkcji, które pobierają argumenty słów kluczowych.

Matt Joiner
źródło
16

defaultdict jest świetny, gdy domyślna wartość jest statyczna, jak nowa lista, ale nie tak bardzo, jeśli jest dynamiczna.

Na przykład potrzebuję słownika do mapowania ciągów znaków na unikalne int. defaultdict(int)zawsze użyje 0 jako wartości domyślnej. Podobnie defaultdict(intGen())zawsze daje 1.

Zamiast tego użyłem zwykłego dykta:

nextID = intGen()
myDict = {}
for lots of complicated stuff:
    #stuff that generates unpredictable, possibly already seen str
    strID = myDict.setdefault(myStr, nextID())

Zauważ, że dict.get(key, nextID())jest to niewystarczające, ponieważ muszę również móc odwoływać się do tych wartości później.

intGen to niewielka klasa, którą buduję, która automatycznie zwiększa liczbę całkowitą i zwraca jej wartość:

class intGen:
    def __init__(self):
        self.i = 0

    def __call__(self):
        self.i += 1
    return self.i

Jeśli ktoś ma na to sposób defaultdict, chciałbym to zobaczyć.

David Kanarek
źródło
Aby to zrobić za pomocą (podklasy) defaultdict, zobacz to pytanie: stackoverflow.com/questions/2912231/...
weronika,
8
Można wymienić intGenz itertools.count().next.
Antimony
7
nextID()wartość będzie zwiększana za każdym razem myDict.setdefault(), gdy zostanie wywołana, nawet jeśli zwracana wartość nie jest używana jako strID. Wydaje się to w pewnym sensie marnotrawstwem i ilustruje jedną z rzeczy, których setdefault()ogólnie nie lubię - a mianowicie to, że zawsze ocenia swój defaultargument, czy faktycznie się przyzwyczai.
martineau
Można to zrobić z defaultdict: myDict = defaultdict(lambda: nextID()). Później strID = myDict[myStr]w pętli.
musiphil
3
Aby uzyskać zachowanie opisane przez defaultdict, dlaczego nie tylko myDict = defaultdict(nextID)?
czterdzieści dwa
10

Używam, setdefault()gdy chcę wartość domyślną w OrderedDict. Nie jest to standardowy zbiór Pythona, który ma zarówno, ale sposoby, aby wdrożyć taką kolekcję.

AndyGeek
źródło
10

Ponieważ większość odpowiedzi podaje setdefaultlub defaultdictpozwala ustawić wartość domyślną, gdy klucz nie istnieje. Chciałbym jednak zwrócić uwagę na małe zastrzeżenie dotyczące przypadków użycia setdefault. Podczas wykonywania interpretera Python setdefaultzawsze ocenia drugi argument funkcji, nawet jeśli klucz istnieje w słowniku. Na przykład:

In: d = {1:5, 2:6}

In: d
Out: {1: 5, 2: 6}

In: d.setdefault(2, 0)
Out: 6

In: d.setdefault(2, print('test'))
test
Out: 6

Jak widać, printzostał również wykonany, mimo że 2 już istniały w słowniku. Staje się to szczególnie ważne, jeśli planujesz setdefaultna przykład użyć do takiej optymalizacji memoization. Jeśli dodasz wywołanie funkcji rekurencyjnej jako drugi argument setdefault, nie uzyskasz żadnej wydajności, ponieważ Python zawsze będzie wywoływał funkcję rekurencyjnie.

Odkąd wspomniano o zapamiętywaniu, lepszą alternatywą jest użycie dekoratora funkools.lru_cache, jeśli rozważasz ulepszenie funkcji za pomocą zapamiętywania. lru_cache lepiej obsługuje wymagania buforowania dla funkcji rekurencyjnej.

picmate 涅
źródło
8

Jak powiedział Mahomet, są sytuacje, w których tylko czasami chcesz ustawić wartość domyślną. Świetnym przykładem tego jest struktura danych, która jest najpierw wypełniana, a następnie odpytywana.

Zastanów się. Podczas dodawania słowa, jeśli podwęzeł jest potrzebny, ale nie jest obecny, należy go utworzyć, aby rozszerzyć trie. Podczas zapytania o obecność słowa brakujący podwęzeł wskazuje, że słowo nie jest obecne i nie należy go tworzyć.

Defaultdict nie może tego zrobić. Zamiast tego należy użyć zwykłego dykta z metodami get i setdefault.

David Kanarek
źródło
5

Teoretycznie setdefaultbyłoby to przydatne, jeśli czasami chcesz ustawić wartość domyślną, a czasem nie. W prawdziwym życiu nie spotkałem się z takim przypadkiem użycia.

Interesujący przypadek użycia pochodzi jednak ze standardowej biblioteki (Python 2.6, _threadinglocal.py):

>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]

Powiedziałbym, że używanie __dict__.setdefaultjest bardzo przydatnym przypadkiem.

Edycja : Tak się składa, że ​​jest to jedyny przykład w standardowej bibliotece i jest on w komentarzu. Być może nie jest to wystarczające uzasadnienie istnienia setdefault. Oto wyjaśnienie:

Obiekty przechowują swoje atrybuty w __dict__atrybucie. Tak się składa, że __dict__atrybut można zapisać w dowolnym momencie po utworzeniu obiektu. Jest to także słownik, a nie defaultdict. Nie jest rozsądne, aby przedmioty w ogólnym przypadku miały __dict__jako takie, defaultdictponieważ spowodowałoby to, że każdy obiekt posiadałby wszystkie prawne identyfikatory jako atrybuty. Nie mogę więc przewidzieć żadnych zmian w __dict__.setdefaultusuwaniu obiektów Pythona , oprócz całkowitego ich usunięcia, jeśli uznano to za nieprzydatne.

Muhammad Alkarouri
źródło
1
Czy możesz rozwinąć - co sprawia, że _dict .setdefault jest szczególnie przydatny?
Eli Bendersky
1
@Eli: Myślę, że chodzi o to, że __dict__jest to implementacja a dictnie a defaultdict.
Katriel
1
W porządku. Nie mam nic przeciwko setdefaultpozostaniu w Pythonie, ale ciekawie jest, że teraz jest prawie bezużyteczne.
Eli Bendersky,
@Eli: Zgadzam się. Nie sądzę, żeby było wystarczająco dużo powodów, aby wprowadzić go dzisiaj, gdyby go nie było. Ale będąc już tam, trudno byłoby argumentować za jego usunięciem, biorąc pod uwagę cały kod, który już go używa.
Muhammad Alkarouri,
1
Plik w ramach programowania obronnego. setdefaultwyraźnie zaznacza, że ​​przypisujesz dyktowi klucz, który może istnieć lub nie, a jeśli nie istnieje, chcesz go utworzyć z wartością domyślną: na przykład d.setdefault(key,[]).append(value). Gdzie indziej w programie robisz to, alist=d[k]gdzie obliczane jest k, i chcesz, aby wyjątek został zgłoszony, jeśli k nie jest w d (co przy domyślnym słowie może wymagać, assert k in da nawetif not ( k in d): raise KeyError
nigel222 10.0915
3

Wadą metody defaultdictover dict( dict.setdefault) jest to, że defaultdictobiekt tworzy nowy element ZA KAŻDYM nieistniejącym kluczem (np. Za pomocą ==, print). Również defaultdictklasa jest na ogół znacznie mniej powszechna niż dictklasa, trudniej jest serializować go jako edytor IME.

Funkcje PS IMO | metody nieprzeznaczone do mutowania obiektu, nie powinny mutować obiektu.

xged
źródło
Nie musi za każdym razem tworzyć nowego obiektu. Zamiast tego możesz równie łatwo to zrobić defaultdict(lambda l=[]: l).
Artyer
6
Nigdy nie rób tego, co sugeruje @Artyer - zmienne ustawienia domyślne cię ugryzą.
Brandon Humpert
2

Oto kilka przykładów setdefault, które pokazują jego przydatność:

"""
d = {}
# To add a key->value pair, do the following:
d.setdefault(key, []).append(value)

# To retrieve a list of the values for a key
list_of_values = d[key]

# To remove a key->value pair is still easy, if
# you don't mind leaving empty lists behind when
# the last value for a given key is removed:
d[key].remove(value)

# Despite the empty lists, it's still possible to 
# test for the existance of values easily:
if d.has_key(key) and d[key]:
    pass # d has some values for key

# Note: Each value can exist multiple times!
"""
e = {}
print e
e.setdefault('Cars', []).append('Toyota')
print e
e.setdefault('Motorcycles', []).append('Yamaha')
print e
e.setdefault('Airplanes', []).append('Boeing')
print e
e.setdefault('Cars', []).append('Honda')
print e
e.setdefault('Cars', []).append('BMW')
print e
e.setdefault('Cars', []).append('Toyota')
print e

# NOTE: now e['Cars'] == ['Toyota', 'Honda', 'BMW', 'Toyota']
e['Cars'].remove('Toyota')
print e
# NOTE: it's still true that ('Toyota' in e['Cars'])
Stefan Gruenwald
źródło
2

Przepisałem zaakceptowaną odpowiedź i ułatwiłem ją początkującym.

#break it down and understand it intuitively.
new = {}
for (key, value) in data:
    if key not in new:
        new[key] = [] # this is core of setdefault equals to new.setdefault(key, [])
        new[key].append(value)
    else:
        new[key].append(value)


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # it is new[key] = []
    group.append(value)



# even simpler with defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append(value) # all keys have a default value of empty list []

Dodatkowo podzieliłem metody na kategorie:

dict_methods_11 = {
            'views':['keys', 'values', 'items'],
            'add':['update','setdefault'],
            'remove':['pop', 'popitem','clear'],
            'retrieve':['get',],
            'copy':['copy','fromkeys'],}
Rachunek różniczkowy
źródło
1

Często używam setdefault, gdy otrzymuję to, ustawiając domyślne (!!!) w słowniku; dość często słownik os.environ:

# Set the venv dir if it isn't already overridden:
os.environ.setdefault('VENV_DIR', '/my/default/path')

Mniej zwięźle wygląda to tak:

# Set the venv dir if it isn't already overridden:
if 'VENV_DIR' not in os.environ:
    os.environ['VENV_DIR'] = '/my/default/path')

Warto zauważyć, że można również użyć wynikowej zmiennej:

venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path')

Ale jest to mniej konieczne niż przedtem, zanim pojawiły się nakazy domyślne.

woodm1979
źródło
1

Kolejny przypadek użycia, który nie wydaje mi się wspomniany powyżej. Czasami przechowujesz pamięć podręczną obiektów według ich identyfikatora, w którym główna instancja znajduje się w pamięci podręcznej, i chcesz ustawić pamięć podręczną, gdy brakuje.

return self.objects_by_id.setdefault(obj.id, obj)

Jest to przydatne, gdy zawsze chcesz zachować jedną instancję dla odrębnego identyfikatora, bez względu na to, jak za każdym razem uzyskujesz obiekt obj. Na przykład, gdy atrybuty obiektu są aktualizowane w pamięci, a zapisywanie w pamięci jest odraczane.

Tuttle
źródło
1

Jeden bardzo ważny przypadek użycia, przez który właśnie natknąłem się: dict.setdefault()jest świetny do wielowątkowego kodu, gdy potrzebujesz tylko jednego obiektu kanonicznego (w przeciwieństwie do wielu obiektów, które są równe).

Na przykład (Int)FlagEnum w Pythonie 3.6.0 ma błąd : jeśli wiele wątków konkuruje o element złożony (Int)Flag, może być więcej niż jeden:

from enum import IntFlag, auto
import threading

class TestFlag(IntFlag):
    one = auto()
    two = auto()
    three = auto()
    four = auto()
    five = auto()
    six = auto()
    seven = auto()
    eight = auto()

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return hash(self.value)

seen = set()

class cycle_enum(threading.Thread):
    def run(self):
        for i in range(256):
            seen.add(TestFlag(i))

threads = []
for i in range(8):
    threads.append(cycle_enum())

for t in threads:
    t.start()

for t in threads:
    t.join()

len(seen)
# 272  (should be 256)

Rozwiązaniem jest użycie setdefault()jako ostatniego kroku zapisywania obliczonego elementu kompozytowego - jeśli inny został już zapisany, wówczas jest on używany zamiast nowego, gwarantując unikalne elementy Enum.

Ethan Furman
źródło
0

[Edytuj] Bardzo źle! Setdefault zawsze wyzwalałby długie obliczenia, a Python był chętny.

Rozwijanie odpowiedzi Tuttle'a. Dla mnie najlepszym przypadkiem użycia jest mechanizm pamięci podręcznej. Zamiast:

if x not in memo:
   memo[x]=long_computation(x)
return memo[x]

który zużywa 3 linie i 2 lub 3 odnośniki, chętnie napisałbym :

return memo.setdefault(x, long_computation(x))
YvesgereY
źródło
Dobry przykład. Nadal uważam, że 3 linie są bardziej zrozumiałe, ale może mój mózg urosnie, by docenić setdefault.
Bob Stein
5
Nie są równoważne. W pierwszym long_computation(x)wywoływana jest tylko wtedy, gdy x not in memo. Natomiast w drugim long_computation(x)zawsze jest nazywany. Tylko przypisanie jest warunkowe, równoważny kod setdefaultwyglądałby tak: v = long_computation(x)/ if x not in memo:/ memo[x] = v.
Dan D.
0

Innym przypadkiem użycia setdefault()jest sytuacja, gdy nie chcesz zastępować wartości już ustawionego klucza. defaultdictnadpisuje, podczas gdy setdefault()nie. W przypadku zagnieżdżonych słowników częściej zdarza się, że chcesz ustawić wartość domyślną tylko wtedy, gdy klucz nie jest jeszcze ustawiony, ponieważ nie chcesz usuwać obecnego słownika podrzędnego. To jest, kiedy używasz setdefault().

Przykład z defaultdict:

>>> from collection import defaultdict()
>>> foo = defaultdict()
>>> foo['a'] = 4
>>> foo['a'] = 2
>>> print(foo)
defaultdict(None, {'a': 2})

setdefault nie zastępuje:

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}
Jodnas
źródło