Anwendungsfälle für die Diktiermethode 'setdefault'

192

Das Hinzufügen von collections.defaultdictin Python 2.5 reduzierte den Bedarf an dict's setdefaultMethode erheblich . Diese Frage ist für unsere kollektive Bildung:

  1. Wofür ist setdefaultheute in Python 2.6 / 2.7 noch nützlich?
  2. Welche populären Anwendungsfälle setdefaultwurden abgelöst collections.defaultdict?
Eli Bendersky
quelle
1
Leicht verwandt zu stackoverflow.com/questions/7423428/…
Benutzer

Antworten:

208

Man könnte sagen, es defaultdictist nützlich, um Standardeinstellungen vor dem Ausfüllen des Diktats festzulegen, und setdefaultist nützlich, um Standardeinstellungen während oder nach dem Ausfüllen des Diktats festzulegen .

Wahrscheinlich der häufigste Anwendungsfall: Gruppieren von Elementen (in unsortierten Daten, sonst Verwendung 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

Manchmal möchten Sie sicherstellen, dass nach dem Erstellen eines Diktats bestimmte Schlüssel vorhanden sind. defaultdictfunktioniert in diesem Fall nicht, da nur Schlüssel bei explizitem Zugriff erstellt werden. Denken Sie, Sie verwenden etwas HTTP-artiges mit vielen Headern - einige sind optional, aber Sie möchten Standardeinstellungen für sie:

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
quelle
1
In der Tat ist dieses IMHO der Hauptanwendungsfall für den Ersatz durch defaultdict. Können Sie im ersten Absatz ein Beispiel geben, was Sie meinen?
Eli Bendersky
2
Muhammad Alkarouri: Zuerst kopieren Sie das Diktat und überschreiben dann einige der Elemente. Ich mache das auch oft und ich denke, das ist eigentlich die Redewendung, die am meisten bevorzugt wird setdefault. A defaultdicthingegen würde nicht funktionieren, wenn nicht alle defaultvaluesgleich sind (dh einige sind 0und einige sind []).
Jochen Ritzel
2
@ YHC4k, ja. Deshalb habe ich verwendet headers = dict(optional_headers). Für den Fall, dass die Standardwerte nicht alle gleich sind. Das Endergebnis ist dasselbe, als ob Sie zuerst die HTTP-Header erhalten und dann die Standardeinstellungen für diejenigen festlegen, die Sie nicht erhalten haben. Und es ist durchaus brauchbar, wenn Sie bereits haben optional_headers. Probieren Sie meinen angegebenen 2-Schritt-Code aus und vergleichen Sie ihn mit Ihrem. Sie werden sehen, was ich meine.
Muhammad Alkarouri
19
oder einfach tunnew.setdefault(key, []).append(value)
fmalina
2
Ich finde es seltsam, dass die beste Antwort defaultdictsogar noch besser ist als setdefault(also wo ist der Anwendungsfall jetzt?). Auch ChainMapwürde besser das httpBeispiel IMO behandeln.
YvesgereY
29

Ich verwende häufig setdefaultfür Schlüsselwortargument-Dikte, wie in dieser Funktion:

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)

Es eignet sich hervorragend zum Optimieren von Argumenten in Wrappern um Funktionen, die Schlüsselwortargumente verwenden.

Matt Joiner
quelle
16

defaultdict ist großartig, wenn der Standardwert statisch ist, wie eine neue Liste, aber nicht so sehr, wenn er dynamisch ist.

Zum Beispiel brauche ich ein Wörterbuch, um Zeichenfolgen eindeutigen Ints zuzuordnen. defaultdict(int)wird immer 0 als Standardwert verwenden. Ebenso defaultdict(intGen())ergibt immer 1.

Stattdessen habe ich ein reguläres Diktat verwendet:

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

Beachten Sie, dass dies dict.get(key, nextID())nicht ausreicht, da ich später auch auf diese Werte verweisen muss.

intGen ist eine winzige Klasse, die ich baue und die ein int automatisch inkrementiert und seinen Wert zurückgibt:

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

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

Wenn jemand eine Möglichkeit hat, dies zu tun, defaultdictwürde ich es gerne sehen.

David Kanarek
quelle
Eine Möglichkeit, dies mit (einer Unterklasse von) defaultdict zu tun, finden Sie in dieser Frage: stackoverflow.com/questions/2912231/…
weronika
8
Sie könnten ersetzen intGenmit itertools.count().next.
Antimon
7
nextID()Der Wert von 'wird bei jedem Aufruf erhöht myDict.setdefault(), auch wenn der zurückgegebene Wert nicht als a verwendet wird strID. Dies scheint irgendwie verschwenderisch und veranschaulicht eines der Dinge, die ich setdefault()im Allgemeinen nicht mag - nämlich, dass es immer seine defaultArgumentation bewertet , ob es tatsächlich verwendet wird oder nicht.
Martineau
Sie können es tun mit defaultdict: myDict = defaultdict(lambda: nextID()). Später strID = myDict[myStr]in der Schleife.
Musiphil
3
Warum nicht einfach, um das Verhalten zu erhalten, das Sie mit defaultdict beschreiben myDict = defaultdict(nextID)?
zweiundvierzig
10

Ich verwende, setdefault()wenn ich einen Standardwert in einem möchte OrderedDict. Es gibt keine Standard-Python-Sammlung, die beides kann, aber es gibt Möglichkeiten , eine solche Sammlung zu implementieren.

AndyGeek
quelle
9

Da die meisten Antworten Zustand setdefaultoder defaultdictwürden Sie einen Standardwert gesetzt , wenn ein Schlüssel nicht existiert. Ich möchte jedoch auf eine kleine Einschränkung in Bezug auf die Anwendungsfälle von hinweisen setdefault. Wenn der Python-Interpreter ausgeführt setdefaultwird, wertet er immer das zweite Argument für die Funktion aus, auch wenn der Schlüssel im Wörterbuch vorhanden ist. Beispielsweise:

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

Wie Sie sehen können, printwurde auch ausgeführt, obwohl 2 bereits im Wörterbuch vorhanden waren. Dies ist besonders wichtig, wenn Sie setdefaultbeispielsweise eine Optimierung wie z memoization. Wenn Sie einen rekursiven Funktionsaufruf als zweites Argument hinzufügen setdefault, erhalten Sie keine Leistung, da Python die Funktion immer rekursiv aufruft.

Da Memoization erwähnt wurde, ist die Verwendung von functools.lru_cache Decorator eine bessere Alternative, wenn Sie eine Funktion durch Memoization erweitern möchten. lru_cache behandelt die Caching-Anforderungen für eine rekursive Funktion besser.

picmate 涅
quelle
8

Wie Muhammad sagte, gibt es Situationen, in denen Sie nur manchmal einen Standardwert festlegen möchten. Ein gutes Beispiel hierfür ist eine Datenstruktur, die zuerst ausgefüllt und dann abgefragt wird.

Betrachten Sie einen Versuch. Wenn beim Hinzufügen eines Wortes ein Unterknoten benötigt wird, der jedoch nicht vorhanden ist, muss er erstellt werden, um den Versuch zu erweitern. Bei der Abfrage nach dem Vorhandensein eines Wortes zeigt ein fehlender Unterknoten an, dass das Wort nicht vorhanden ist und nicht erstellt werden sollte.

Ein Standarddikt kann dies nicht tun. Stattdessen muss ein reguläres Diktat mit den Methoden get und setdefault verwendet werden.

David Kanarek
quelle
5

Theoretisch setdefaultwäre es immer noch praktisch, wenn Sie manchmal einen Standard festlegen möchten und manchmal nicht. Im wirklichen Leben bin ich auf einen solchen Anwendungsfall nicht gestoßen.

Ein interessanter Anwendungsfall ergibt sich jedoch aus der Standardbibliothek (Python 2.6, _threadinglocal.py):

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

Ich würde sagen, dass die Verwendung __dict__.setdefaultein ziemlich nützlicher Fall ist.

Bearbeiten : Dies ist zufällig das einzige Beispiel in der Standardbibliothek und befindet sich in einem Kommentar. Vielleicht reicht es nicht aus, die Existenz von zu rechtfertigen setdefault. Dennoch ist hier eine Erklärung:

Objekte speichern ihre Attribute im __dict__Attribut. Das __dict__Attribut ist jederzeit nach der Objekterstellung beschreibbar. Es ist auch ein Wörterbuch, kein defaultdict. Es ist nicht sinnvoll, Objekte im allgemeinen Fall als zu haben, __dict__da dadurch defaultdictjedes Objekt alle legalen Kennungen als Attribute hat. Daher kann ich keine Änderung an Python-Objekten vorhersehen __dict__.setdefault, die entfernt werden, abgesehen davon, dass sie vollständig gelöscht werden, wenn sie als nicht nützlich erachtet wurden.

Muhammad Alkarouri
quelle
1
Könnten Sie näher erläutern - was macht _dict .setdefault besonders nützlich?
Eli Bendersky
1
@Eli: Ich denke, der Punkt ist, dass __dict__durch die Implementierung a dict, nicht a defaultdict.
Katriel
1
In Ordung. Es macht mir nichts aus setdefault, in Python zu bleiben, aber es ist merkwürdig zu sehen, dass es jetzt fast nutzlos ist.
Eli Bendersky
@ Eli: Ich stimme zu. Ich glaube nicht, dass es genug Gründe gibt, es heute einzuführen, wenn es nicht da wäre. Da es jedoch bereits vorhanden ist, ist es schwierig zu argumentieren, es zu entfernen, da der gesamte Code es bereits verwendet.
Muhammad Alkarouri
1
Datei unter defensiver Programmierung. setdefaultmacht deutlich, dass Sie einem Diktat über einen Schlüssel zuweisen, der möglicherweise vorhanden ist oder nicht, und wenn er nicht vorhanden ist, möchten Sie, dass er mit einem Standardwert erstellt wird: zum Beispiel d.setdefault(key,[]).append(value). An anderer Stelle im Programm tun Sie, alist=d[k]wo k berechnet wird, und Sie möchten eine Ausnahme auslösen, wenn k nicht in d ist (was bei einem Standarddikt möglicherweise erforderlich ist assert k in doder sogarif not ( k in d): raise KeyError
nigel222
3

Ein Nachteil von defaultdictover dict( dict.setdefault) ist, dass ein defaultdictObjekt ein neues Element erstellt. JEDERZEIT wird ein nicht vorhandener Schlüssel angegeben (z . B. mit ==, print). Auch die defaultdictKlasse ist im Allgemeinen viel seltener als die dictKlasse, es ist schwieriger, sie IME zu serialisieren.

PS IMO-Funktionen | Methoden, die kein Objekt mutieren sollen, sollten ein Objekt nicht mutieren.

xged
quelle
Es muss nicht jedes Mal ein neues Objekt erstellt werden. Sie können dies genauso einfach tun defaultdict(lambda l=[]: l).
Artyer
6
Tun Sie niemals das, was @Artyer vorschlägt - veränderbare Standardeinstellungen werden Sie beißen.
Brandon Humpert
2

Hier sind einige Beispiele für setdefault, um seine Nützlichkeit zu demonstrieren:

"""
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
quelle
2

Ich habe die akzeptierte Antwort umgeschrieben und sie für die Neulinge erleichtert.

#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 []

Zusätzlich habe ich die Methoden als Referenz kategorisiert:

dict_methods_11 = {
            'views':['keys', 'values', 'items'],
            'add':['update','setdefault'],
            'remove':['pop', 'popitem','clear'],
            'retrieve':['get',],
            'copy':['copy','fromkeys'],}
Infinitesimalrechnung
quelle
1

Ich benutze setdefault häufig, wenn ich dies bekomme und einen Standard (!!!) in einem Wörterbuch setze. etwas häufig das os.environ Wörterbuch:

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

Weniger prägnant sieht das so aus:

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

Es ist erwähnenswert, dass Sie auch die resultierende Variable verwenden können:

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

Aber das ist weniger notwendig als vor dem Bestehen von Standarddiktaten.

woodm1979
quelle
1

Ein anderer Anwendungsfall, den ich nicht glaube, wurde oben erwähnt. Manchmal behalten Sie ein Cache-Diktat von Objekten anhand ihrer ID bei, wobei sich die primäre Instanz im Cache befindet, und Sie möchten den Cache festlegen, wenn dieser fehlt.

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

Dies ist nützlich, wenn Sie immer eine einzelne Instanz pro eindeutiger ID behalten möchten, unabhängig davon, wie Sie jedes Mal ein Objekt erhalten. Zum Beispiel, wenn Objektattribute im Speicher aktualisiert werden und das Speichern im Speicher verschoben wird.

Tuttle
quelle
1

Ein sehr wichtiger Anwendungsfall, auf den ich gerade gestoßen bin: Er dict.setdefault()eignet sich hervorragend für Multithread-Code, wenn Sie nur ein einziges kanonisches Objekt möchten (im Gegensatz zu mehreren Objekten, die zufällig gleich sind).

Beispielsweise weist die (Int)FlagAufzählung in Python 3.6.0 einen Fehler auf : Wenn mehrere Threads um ein zusammengesetztes (Int)FlagMitglied konkurrieren , gibt es möglicherweise mehr als einen:

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)

Die Lösung besteht darin, setdefault()als letzten Schritt das Speichern des berechneten zusammengesetzten Elements zu verwenden. Wenn bereits ein anderes Element gespeichert wurde, wird es anstelle des neuen Elements verwendet, wodurch eindeutige Enum-Elemente garantiert werden.

Ethan Furman
quelle
0

[Bearbeiten] Sehr falsch! Der setdefault würde immer long_computation auslösen, wobei Python eifrig ist.

Erweiterung von Tuttles Antwort. Für mich ist der Cache-Mechanismus der beste Anwendungsfall. Anstatt:

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

das 3 Zeilen und 2 oder 3 Lookups verbraucht, würde ich gerne schreiben :

return memo.setdefault(x, long_computation(x))
YvesgereY
quelle
Gutes Beispiel. Ich denke immer noch, dass die 3 Zeilen verständlicher sind, aber vielleicht wird mein Gehirn wachsen, um Setdefault zu schätzen.
Bob Stein
5
Diese sind nicht gleichwertig. Im ersten long_computation(x)wird nur wenn aufgerufen x not in memo. Während im zweiten long_computation(x)immer aufgerufen wird. Nur die Zuweisung ist bedingt, der entsprechende Code setdefaultwürde folgendermaßen aussehen: v = long_computation(x)/ if x not in memo:/ memo[x] = v.
Dan D.
0

Der andere Anwendungsfall setdefault()besteht darin, dass Sie den Wert eines bereits festgelegten Schlüssels nicht überschreiben möchten . defaultdictüberschreibt, während setdefault()nicht. Bei verschachtelten Wörterbüchern ist es häufiger der Fall, dass Sie nur dann einen Standard festlegen möchten, wenn der Schlüssel noch nicht festgelegt ist, da Sie das aktuelle Unterwörterbuch nicht entfernen möchten. Dies ist, wenn Sie verwenden setdefault().

Beispiel mit defaultdict:

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

setdefault überschreibt nicht:

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}
Iodnas
quelle