wake-up-neo.com

python-Dikt: Get vs Setdefault

Die folgenden zwei Ausdrücke scheinen mir gleich zu sein. Welches ist vorzuziehen?

data = [('a', 1), ('b', 1), ('b', 2)]

d1 = {}
d2 = {}

for key, val in data:
    # variant 1)
    d1[key] = d1.get(key, []) + [val]
    # variant 2)
    d2.setdefault(key, []).append(val)

Die Ergebnisse sind die gleichen, aber welche Version ist besser oder eher pythonisch?

Ich persönlich finde Version 2 schwieriger zu verstehen, da setdefault für mich sehr schwierig zu verstehen ist. Wenn ich es richtig verstanden habe, sucht es nach dem Wert von "key" im Wörterbuch, wenn es nicht verfügbar ist, gibt "[]" in das Diktat ein, gibt einen Verweis auf den Wert oder "[]" zurück und fügt "val" hinzu Referenz. Während es sicherlich glatt ist, ist es zumindest nicht intuitiv (zumindest für mich).

Version 1 ist meiner Meinung nach einfacher zu verstehen (wenn verfügbar, holen Sie sich den Wert für "key", wenn nicht, holen Sie "[]"), fügen Sie dann eine Liste aus [val] hinzu und geben Sie das Ergebnis in "key" ein. ). Aber obwohl es intuitiver zu verstehen ist, fürchte ich, dass diese Version weniger performant ist, da all diese Listen erstellt werden. Ein weiterer Nachteil besteht darin, dass "d1" zweimal in dem Ausdruck auftritt, der eher fehleranfällig ist. Wahrscheinlich gibt es eine bessere Implementierung mit get, aber momentan entgeht es mir.

Meine Vermutung ist, dass Version 2, obwohl für Unerfahrene schwieriger zu erfassen, schneller und daher vorzuziehen ist. Meinungen

39
Cerno

Ihre beiden Beispiele machen dasselbe, aber get und setdefault tun dies nicht. 

Der Unterschied zwischen den beiden besteht im Wesentlichen in der manuellen Einstellung von d[key], um jedes Mal auf die Liste zu verweisen, während setdefault automatisch d[key] nur dann auf die Liste setzt, wenn sie nicht gesetzt ist.

Ich machte die beiden Methoden so ähnlich wie möglich

from timeit import timeit

print timeit("c = d.get(0, []); c.extend([1]); d[0] = c", "d = {1: []}", number = 1000000)
print timeit("c = d.get(1, []); c.extend([1]); d[0] = c", "d = {1: []}", number = 1000000)
print timeit("d.setdefault(0, []).extend([1])", "d = {1: []}", number = 1000000)
print timeit("d.setdefault(1, []).extend([1])", "d = {1: []}", number = 1000000)

und bekam 

0.794723378711
0.811882272256
0.724429205999
0.722129751973

Daher ist setdefault um 10% schneller als get.

Mit der get-Methode können Sie less als mit setdefault ausführen. Sie können es verwenden, um zu vermeiden, dass eine KeyError angezeigt wird, wenn der Schlüssel nicht existiert (wenn dies häufig vorkommt), auch wenn Sie den Schlüssel nicht festlegen möchten.

Siehe Anwendungsfälle für die 'setdefault' dict-Methode und dict.get () - Methode gibt einen Zeiger zurück für weitere Informationen zu den beiden Methoden.

Der Thread über setdefault schließt daraus, dass Sie meistens eine defaultdict verwenden möchten. Der Thread über get kommt zu dem Schluss, dass er langsam ist und es häufig besser ist, wenn Sie doppelt nachschlagen, ein Standarddict verwenden oder den Fehler behandeln (abhängig von der Größe des Wörterbuchs und Ihrem Anwendungsfall).

21
agf

Die akzeptierte Antwort von agf ist nicht vergleichbar mit Gleichem. Nach dem:

print timeit("d[0] = d.get(0, []) + [1]", "d = {1: []}", number = 10000)

d[0] enthält eine Liste mit 10.000 Artikeln, wobei nach:

print timeit("d.setdefault(0, []) + [1]", "d = {1: []}", number = 10000)

d[0] ist einfach []. die d.setdefault-Version ändert niemals die in d gespeicherte Liste. Der Code sollte eigentlich sein:

print timeit("d.setdefault(0, []).append(1)", "d = {1: []}", number = 10000)

und ist in der Tat schneller als das fehlerhafte setdefault-Beispiel.

Der Unterschied hier besteht wirklich darin, dass beim Anhängen mittels Verkettung die gesamte Liste jedes Mal kopiert wird (und sobald 10.000 Elemente vorhanden sind, die messbar werden. Mit append werden die Aktualisierungen der Liste O (1), d. H. Eine konstante Zeit, amortisiert.

Schließlich gibt es zwei weitere Optionen, die in der ursprünglichen Frage nicht berücksichtigt werden: defaultdict oder einfach das Wörterbuch testen, um festzustellen, ob es bereits den Schlüssel enthält.

Angenommen, d3, d4 = defaultdict(list), {}

# variant 1 (0.39)
d1[key] = d1.get(key, []) + [val]
# variant 2 (0.003)
d2.setdefault(key, []).append(val)
# variant 3 (0.0017)
d3[key].append(val)
# variant 4 (0.002)
if key in d4:
    d4[key].append(val)
else:
    d4[key] = [val]

variante 1 ist bei weitem die langsamste, weil sie die Liste jedes Mal kopiert, Variante 2 ist die zweit langsamste, Variante 3 ist die schnellste, funktioniert jedoch nicht, wenn Sie Python älter als 2.5 benötigen, und Variante 4 ist etwas langsamer als Variante 3 .

Ich würde sagen, verwenden Sie Variante 3, wenn Sie können, mit Variante 4 als Option für die gelegentlichen Orte, an denen defaultdict nicht genau passt. Vermeiden Sie beide Ihrer ursprünglichen Varianten.

14
Duncan

Vielleicht möchten Sie defaultdict im Modul collections betrachten. Folgendes entspricht Ihren Beispielen.

from collections import defaultdict

data = [('a', 1), ('b', 1), ('b', 2)]

d = defaultdict(list)

for k, v in data:
    d[k].append(v)

Es gibt mehr hier .

10
grifaton

1. Hier mit einem guten Beispiel erklärt:
http://code.activestate.com/recipes/66516-add-an-entry-to-a-dictionary-unless-the-entry-is-a/

dict. setdefault typische Verwendung
somedict.setdefault(somekey,[]).append(somevalue)

dict. get typische Verwendung
theIndex[Word] = 1 + theIndex.get(Word,0)


2. Weitere Erklärung: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html

dict.setdefault() entspricht get oder set & get. Oder set if necessary then get. Es ist besonders effizient, wenn die Berechnung Ihres Wörterbuchschlüssels teuer oder die Eingabe langwierig ist.

Das einzige Problem mit dict.setdefault () ist, dass der Standardwert immer ausgewertet wird, unabhängig davon, ob er benötigt wird oder nicht. Das ist nur wichtig, wenn der Standardwert teuer in der Berechnung ist. Verwenden Sie in diesem Fall defaultdict.


3. Schließlich werden die offiziellen Dokumente mit Unterschieden hervorgehoben http://docs.python.org/2/library/stdtypes.html

get(key[, default])
Gibt den Wert für key zurück, wenn key im Wörterbuch enthalten ist, andernfalls default. Wenn die Standardeinstellung nicht angegeben ist, wird standardmäßig Keine verwendet, sodass diese Methode niemals einen KeyError auslöst.

setdefault(key[, default])
Wenn sich der Schlüssel im Wörterbuch befindet, geben Sie seinen Wert zurück. Wenn nicht, Schlüssel einfügen mit dem Wert default und return default. Die Standardeinstellung ist Keine.

3
user

Für diejenigen, die immer noch Schwierigkeiten haben, diese beiden Begriffe zu verstehen, lassen Sie mich Ihnen den grundlegenden Unterschied zwischen der get () - und der setdefault () -Methode erklären. 

Szenario 1

root = {}
root.setdefault('A', [])
print(root)

Szenario 2

root = {}
root.get('A', [])
print(root)

In Szenario-1 wird {'A': []} ausgegeben, in Szenario-2 {}.

So setzt setdefault() abwesende Schlüssel in das Diktat, während get() nur den Standardwert bereitstellt, das Wörterbuch jedoch nicht ändert.

Lassen Sie uns kommen, wo dies nützlich ist - Angenommen, Sie suchen nach einem Element in einem Diktat, dessen Wert eine Liste ist, und Sie möchten diese Liste ändern, wenn Sie sonst einen neuen Schlüssel mit dieser Liste erstellen.

setdefault() verwenden

def fn1(dic, key, lst):
    dic.setdefault(key, []).extend(lst)

get() verwenden

def fn2(dic, key, lst):
    dic[key] = dic.get(key, []) + (lst) #Explicit assigning happening here

Jetzt können wir Timings untersuchen - 

dic = {}
%%timeit -n 10000 -r 4
fn1(dic, 'A', [1,2,3])

Es dauerte 288 ns

dic = {}
%%timeit -n 10000 -r 4
fn2(dic, 'A', [1,2,3])

Dauerte 128 s

Es gibt also einen sehr großen zeitlichen Unterschied zwischen diesen beiden Ansätzen.

1
pyAddict
In [1]: person_dict = {}

In [2]: person_dict['liqi'] = 'LiQi'

In [3]: person_dict.setdefault('liqi', 'Liqi')
Out[3]: 'LiQi'

In [4]: person_dict.setdefault('Kim', 'kim')
Out[4]: 'kim'

In [5]: person_dict
Out[5]: {'Kim': 'kim', 'liqi': 'LiQi'}

In [8]: person_dict.get('Dim', '')
Out[8]: ''

In [5]: person_dict
Out[5]: {'Kim': 'kim', 'liqi': 'LiQi'}
1
youtoce

Die Logik von dict.get lautet:

if key in a_dict:
    value = a_dict[key] 
else: 
    value = default_value

Nehmen Sie ein Beispiel:

In [72]: a_dict = {'mapping':['dict', 'OrderedDict'], 'array':['list', 'Tuple']}
In [73]: a_dict.get('string', ['str', 'bytes'])
Out[73]: ['str', 'bytes']
In [74]: a_dict.get('array', ['str', 'byets'])
Out[74]: ['list', 'Tuple']

Der Mechamismus von setdefault lautet:

    levels = ['master', 'manager', 'salesman', 'accountant', 'assistant']
    #group them by the leading letter
    group_by_leading_letter = {}
    # the logic expressed by obvious if condition
    for level in levels:
        leading_letter = level[0]
        if leading_letter not in group_by_leading_letter:
            group_by_leading_letter[leading_letter] = [level]
        else:
            group_by_leading_letter[leading_letter].append(Word)
    In [80]: group_by_leading_letter
    Out[80]: {'a': ['accountant', 'assistant'], 'm': ['master', 'manager'], 's': ['salesman']}

Die Methode setdefault dict dient genau diesem Zweck. Die vorangehende for-Schleife kann wie folgt umgeschrieben werden:

In [87]: for level in levels:
    ...:     leading = level[0]
    ...:     group_by_leading_letter.setdefault(leading,[]).append(level)
Out[80]: {'a': ['accountant', 'assistant'], 'm': ['master', 'manager'], 's': ['salesman']}

Es ist sehr einfach, dh entweder eine Nicht-Null-Liste hängt ein Element an oder eine Null-Liste hängt ein Element an.

Die defaultdict macht das noch einfacher. Um einen zu erstellen, übergeben Sie einen Typ oder eine Funktion zum Generieren des Standardwerts für jeden Schlitz im Diktum:

from collections import defualtdict
group_by_leading_letter = defaultdict(list)
for level in levels:
    group_by_leading_letter[level[0]].append(level)
0
JawSaw