webentwicklung-frage-antwort-db.com.de

Anwendungsfälle für die Dict-Methode 'setdefault'

Durch den Zusatz von collections.defaultdict in Python 2.5 wurde die dict-Methode von setdefault erheblich reduziert. Diese Frage ist für unsere kollektive Ausbildung:

  1. Was ist setdefault heute noch in Python 2.6/2.7 nützlich?
  2. Welche populären Anwendungsfälle von setdefault wurden durch collections.defaultdict ersetzt?
166
Eli Bendersky

Man könnte sagen, defaultdict ist nützlich für Standardeinstellungen vor dem Ausfüllen des Diktiers und setdefault ist nützlich für das Festlegen von Standardwerten während oder nach dem Ausfüllen des Diktiers

Wahrscheinlich der häufigste Anwendungsfall: Gruppieren von Elementen (bei unsortierten Daten verwenden Sie 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 
new = defaultdict(list)
for (key, value) in data:
    new[key].append( value ) # all keys have a default already

Manchmal möchten Sie sicherstellen, dass bestimmte Schlüssel vorhanden sind, nachdem Sie ein Diktat erstellt haben. defaultdict funktioniert in diesem Fall nicht, da Schlüssel nur beim expliziten Zugriff erstellt werden. Stellen Sie sich vor, Sie verwenden etwas HTTP-ish mit vielen Kopfzeilen - einige sind optional, Sie möchten jedoch Standardwerte verwenden:

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 )
177
Jochen Ritzel

Ich benutze häufig setdefault für Keyword-Argumente, wie zum Beispiel 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 für die Anpassung von Argumenten in Wrapper an Funktionen, die Schlüsselwortargumente enthalten.

28
Matt Joiner

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

Zum Beispiel brauche ich ein Wörterbuch, um Zeichenfolgen eindeutigen Ints zuzuordnen. defaultdict(int) verwendet immer 0 für den Standardwert. Ebenso erzeugt defaultdict(intGen()) immer 1.

Stattdessen verwendete ich ein reguläres Diktat:

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

Beachten Sie, dass dict.get(key, nextID()) nicht ausreicht, da ich später auf diese Werte zurückgreifen muss.

intGen ist eine kleine Klasse, die ich baue, die ein int automatisch erhöht 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 mit defaultdict zu tun, würde ich es gerne sehen.

15
David Kanarek

Ich benutze setdefault(), wenn ich einen Standardwert in einer OrderedDict möchte. Es gibt keine Standard-Python-Collection, die beides tut, aber areways , um eine solche Collection zu implementieren.

9
AndyGeek

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

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

Ein Defaultdict kann das nicht. Stattdessen muss ein reguläres Diktat mit den Methoden get und setdefault verwendet werden.

7
David Kanarek

Theoretisch wäre setdefault immer noch nützlich, wenn Sie manchmal einen Standard setzen möchten und manchmal nicht. Im wirklichen Leben bin ich keinem solchen Anwendungsfall begegnet.

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 von __dict__.setdefault ein ziemlich nützlicher Fall ist.

Edit : Dies ist das einzige Beispiel in der Standardbibliothek und in einem Kommentar. Möglicherweise reicht es nicht aus, die Existenz von setdefault zu rechtfertigen. Hier ist noch eine Erklärung:

Objekte speichern ihre Attribute im __dict__-Attribut. Zufällig ist das __dict__-Attribut jederzeit nach der Objekterstellung schreibbar. Es ist auch ein Wörterbuch und keine defaultdict. Im allgemeinen Fall ist es für Objekte nicht sinnvoll, __dict__ als defaultdict zu verwenden, da dies jedes Objekt mit allen zulässigen Bezeichnern als Attribut machen würde. Daher kann ich keine Änderung an Python-Objekten vorhersehen, die __dict__.setdefault entfernen. Abgesehen davon, dass sie vollständig gelöscht werden, wenn dies als nicht nützlich erachtet wird.

5

Ein Nachteil von defaultdict gegenüber dict (dict.setdefault) besteht darin, dass ein defaultdict-Objekt jedes Mal ein neues ElementERSTELLT, WENN EINnicht vorhandener Schlüssel angegeben wird (zB mit ==, print). Auch die defaultdict-Klasse ist im Allgemeinen weitaus weniger verbreitet als die dict-Klasse, ihre IME ist schwieriger zu serialisieren.

P.S. IMO-Funktionen, die kein Objekt mutieren sollen, sollten ein Objekt nicht mutieren.

2
xged

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

"""
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'])
2

Bei den meisten Antworten können Sie mit setdefault oder defaultdict einen Standardwert festlegen, wenn ein Schlüssel nicht vorhanden ist. Ich möchte jedoch auf einen kleinen Vorbehalt hinsichtlich der Anwendungsfälle von setdefault hinweisen. Wenn der Python-Interpreter ausgeführt wird, wertet setdefaultit immer das zweite Argument der Funktion aus, selbst wenn der Schlüssel im Wörterbuch vorhanden ist. Zum Beispiel:

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, wurde auch print ausgeführt, obwohl 2 bereits im Wörterbuch vorhanden war. Dies ist besonders wichtig, wenn Sie setdefault zum Beispiel für eine Optimierung wie memoization verwenden möchten. Wenn Sie einen rekursiven Funktionsaufruf als zweites Argument zu setdefault hinzufügen, erhalten Sie keine Performance, da Python die Funktion immer rekursiv aufrufen würde.

1
picmate 涅

Ein anderer Anwendungsfall, von dem ich glaube, dass er oben nicht erwähnt wurde ... .. Manchmal halten Sie ein Cache-Diktat von Objekten anhand ihrer ID, deren primäre Instanz sich im Cache befindet, und Sie möchten den Cache festlegen, wenn er 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.

1
Tuttle

Ich verwende setdefault häufig, wenn Sie dies tun und einen Standardwert (!!!) in einem Wörterbuch festlegen. etwas allgemein 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')

Dies ist jedoch weniger notwendig als vor dem Auftreten von Defaultdicts.

1
woodm1979

Ein sehr wichtiger Anwendungsfall, über den ich gerade gestolpert bin: dict.setdefault() eignet sich hervorragend für Multithread-Code, wenn Sie nur ein einzelnes kanonisches Objekt wünschen (im Gegensatz zu mehreren Objekten, die gleich sind).

Das (Int)Flag-Enum in Python 3.6.0 weist beispielsweise einen Fehler auf : Wenn mehrere Threads um ein zusammengesetztes (Int)Flag-Mitglied konkurrieren, kann dies zu mehr als einem führen:

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 zum Speichern des berechneten Verbundelements zu verwenden. Wenn bereits ein anderes gespeichert wurde, wird dieses anstelle des neuen Elements verwendet, um eindeutige Enum-Mitglieder zu gewährleisten.

1
Ethan Furman

Der andere Anwendungsfall für setdefault() ist wenn Sie nicht den Wert eines bereits gesetzten Schlüssels überschreiben wollen. defaultdict überschreibt, während setdefault() dies nicht tut. 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, weil Sie das aktuelle Unterwörterbuch nicht entfernen möchten. Dies ist, wenn Sie setdefault() verwenden.

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

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

Erweiterung auf Tuttles Antwort. Der beste Anwendungsfall ist für mich der Cache-Mechanismus. Anstatt:

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

was 3 Zeilen und 2 oder 3 Suchen benötigt, Ich würde gerne schreiben :

return memo.setdefault(x, long_computation(x))
0
YvesgereY

Ich habe die akzeptierte Antwort umgeschrieben und für Neulinge zugänglich gemacht.

#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'],}
0
JawSaw

Ich mag die Antwort hier:

http://stupidpythonideas.blogspot.com/2013/08/defaultdict-vs-setdefault.html

Kurz gesagt, die Entscheidung (in nicht leistungskritischen Apps) sollte auf der Grundlage der Art und Weise getroffen werden, in der nachgeschaltete leere Schlüssel gesucht werden sollen (viz.KeyError im Vergleich zum Standardwert).

0
Fred