web-dev-qa-db-fra.com

dict python: get vs setdefault

Les deux expressions suivantes me semblent équivalentes. Lequel est préférable?

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)

Les résultats sont les mêmes mais quelle version est meilleure ou plutôt plus pythonique?

Personnellement, je trouve la version 2 plus difficile à comprendre, car setdefault est très difficile à comprendre. Si je comprends bien, il cherche la valeur de "clé" dans le dictionnaire, s'il n'est pas disponible, entre "[]" dans le dictionnaire, renvoie une référence à la valeur ou "[]" et ajoute "val" à celui-ci. référence. Bien que certainement lisse, ce n’est pas du tout intuitif (du moins pour moi).

Pour moi, la version 1 est plus facile à comprendre (si disponible, obtenez la valeur pour "clé", sinon, "[]", puis rejoignez une liste constituée de [val] et placez le résultat dans "clé" ). Mais bien que plus intuitive à comprendre, je crains que cette version ne soit moins performante, avec toute cette liste créée. Un autre inconvénient est que "d1" apparaît deux fois dans l'expression, ce qui est plutôt sujet aux erreurs. Il y a probablement une meilleure implémentation en utilisant get, mais cela m’échappe pour l’instant.

À mon avis, la version 2, bien que plus difficile à comprendre pour les inexpérimentés, est plus rapide et donc préférable. Des avis?

39
Cerno

Vos deux exemples font la même chose, mais cela ne signifie pas que get et setdefault do. 

La différence entre les deux réside essentiellement dans le réglage manuel de d[key] pour qu'il pointe vers la liste à chaque fois, par rapport à setdefault pour le réglage automatique de d[key] dans la liste uniquement lorsqu'il n'est pas défini.

En rendant les deux méthodes aussi similaires que possible, j'ai couru

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)

et j'ai 

0.794723378711
0.811882272256
0.724429205999
0.722129751973

Donc setdefault est environ 10% plus rapide que get à cette fin.

La méthode get vous permet de faire moins que vous ne pouvez le faire avec setdefault. Vous pouvez l'utiliser pour éviter d'obtenir une KeyError lorsque la clé n'existe pas (si cela se produit fréquemment) même si vous ne souhaitez pas définir la clé.

Voir Cas d'utilisation de la méthode dict 'setdefault' et La méthode dict.get () renvoie un pointeur pour plus d'informations sur les deux méthodes.

Le fil de discussion sur setdefault conclut que la plupart du temps, vous souhaitez utiliser un defaultdict. Le fil de discussion sur get conclut qu'il est lent et que souvent, vous faites mieux (en termes de vitesse) de faire une double recherche, en utilisant un defaultdict, ou de gérer l'erreur (selon la taille du dictionnaire et votre cas d'utilisation).

21
agf

La réponse acceptée par agf ne compare pas pareil. Après:

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

d[0] contient une liste de 10 000 éléments alors que:

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

d[0] est tout simplement []. c'est-à-dire que la version d.setdefault ne modifie jamais la liste stockée dans d. Le code devrait en réalité être:

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

et est en fait plus rapide que l'exemple setdefault défectueux.

La différence ici réside vraiment dans le fait que lorsque vous ajoutez en utilisant la concaténation, toute la liste est copiée à chaque fois (et une fois que vous avez 10 000 éléments qui commencent à devenir mesurables. En utilisant append, les mises à jour de la liste sont amorties.

Enfin, il y a deux autres options non prises en compte dans la question initiale: defaultdict ou simplement tester le dictionnaire pour voir s'il contient déjà la clé.

Donc, en supposant que 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]

la variante 1 est de loin la plus lente car elle copie la liste à chaque fois, la variante 2 est la deuxième plus lente, la variante 3 est la plus rapide mais ne fonctionnera pas si vous avez besoin de Python plus vieux que 2.5, et la variante 4 est légèrement plus lente que la variante 3 .

Je dirais que vous pouvez utiliser la variante 3 si vous le pouvez, avec la variante 4 en option pour les endroits occasionnels où defaultdict n'est pas un ajustement exact. Évitez les deux de vos variantes d'origine.

14
Duncan

Vous voudrez peut-être consulter defaultdict dans le module collections. Ce qui suit est équivalent à vos exemples.

from collections import defaultdict

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

d = defaultdict(list)

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

Il y a plus ici .

10
grifaton

Pour ceux qui ont encore du mal à comprendre ces deux termes, laissez-moi vous dire la différence fondamentale entre les méthodes get () et setdefault () - 

Scénario 1

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

Scénario 2

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

Dans le scénario 1, la sortie sera {'A': []} tandis que dans le scénario 2, {}

Donc, setdefault() définit les clés absentes dans le dict tandis que get() ne vous fournit que la valeur par défaut, mais ne modifie pas le dictionnaire.

Maintenant, venons là où cela sera utile -- Supposons que vous cherchiez un élément dans un dict dont la valeur est une liste et que vous souhaitiez modifier cette liste s’il est trouvé, sinon créez une nouvelle clé avec cette liste.

en utilisant setdefault()

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

en utilisant get()

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

Maintenant examinons les timings - 

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

A pris 288 ns

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

A pris 128 s

Il existe donc une très grande différence de calendrier entre ces deux approches.

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

La logique de dict.get est la suivante:

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

Prenons un exemple:

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']

Le méchamisme de setdefault est:

    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']}

La méthode setdefault dict répond précisément à cet objectif. La précédente boucle for peut être réécrite comme suit:

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']}

C’est très simple, cela signifie qu’une liste non nulle ajoute un élément ou une liste nulle ajoute un élément.

Le defaultdict, ce qui rend encore plus facile. Pour en créer un, vous transmettez un type ou une fonction permettant de générer la valeur par défaut pour chaque emplacement du dict:

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