Y a-t-il une différence entre l'utilisation d'un littéral dict et d'un constructeur dict?

204

En utilisant PyCharm, j'ai remarqué qu'il propose de convertir un dict littéral :

d = {
    'one': '1',
    'two': '2',
}

dans un constructeur de dict :

d = dict(one='1', two='2')

Ces différentes approches diffèrent-elles de manière significative?

(En écrivant cette question, j'ai remarqué qu'il dict()est impossible de spécifier une clé numérique à l' aide ... d = {1: 'one', 2: 'two'}est possible, mais, évidemment, dict(1='one' ...)ne l'est pas. Autre chose?)

maligree
la source
4
dict()prend une liste de paires clé-valeur ainsi que d'autoriser les paramètres nommés, afin qu'il puisse utiliser utilisé pour créer tout type de dict, mais pas avec la syntaxe que vous utilisez. Cela ne vaut probablement rien non plus qu'il y avait un bogue ( youtrack.jetbrains.net/issue/PY-2512 ) dans pyCharm spécifiquement à cause de ce que vous avez découvert, qui a été corrigé).
Wooble
1
en relation: stackoverflow.com/questions/5790860/… (résumé: le comportement de PyCharm est plus lent et plus laid)
Wooble
1
Apparemment, CPython 2.7 dict () est plus lent (6 fois plus lent?). Voir: doughellmann.com/2012/11// Dans tous les cas, je commence de toute façon à préférer la syntaxe du constructeur car je trouve plus facile de taper et de déplacer du code entre les dict et les appels de fonction.
David Wheaton
2
N'oubliez pas les espaces: vous ne pouvez pas créer de clés contenant des espaces en utilisant la deuxième méthode. La première façon, cependant, peut prendre n'importe quelle chaîne, cela ne le dérange pas. La même chose s'applique à Unicode, bien sûr.
CamilB
2
En Python 2, le dict(abc = 123)constructeur produit un dictionnaire avec des clés de chaîne d'octets 'abc', ce qui peut être surprenant si vous utilisez unicode_literalset attendez que les clés de dictionnaire soient unicode u'abc'. Voir stackoverflow.com/questions/20357210/… .
Li-aung Yip

Réponses:

116

Je pense que vous avez souligné la différence la plus évidente. Mis à part cela,

le premier n'a pas besoin de rechercher dictce qui devrait le rendre un peu plus rapide

le second regarde dictdans locals()puis globals()et les trouvailles BUILTIN, de sorte que vous pouvez changer le comportement en définissant un local appelé dictpar exemple , bien que je ne peux pas penser à n'importe où ce serait une bonne idée à part peut - être quand le débogage

John La Rooy
la source
4
Un exemple de cas où un dict local appelé pourrait être utile: stackoverflow.com/a/7880276/313113
bitek
Je crois également que l'utilisation de dict () va d'abord construire un dict pour les arguments à dict () et ensuite créer un second dict pour l'instance de dict réelle à créer. Les accolades créent l'instance de dict en une seule étape.
NeilG
56

Le littéral est beaucoup plus rapide, car il utilise les opcodes BUILD_MAP et STORE_MAP optimisés plutôt que CALL_FUNCTION générique:

> python2.7 -m timeit "d = dict(a=1, b=2, c=3, d=4, e=5)"
1000000 loops, best of 3: 0.958 usec per loop

> python2.7 -m timeit "d = {'a':1, 'b':2, 'c':3, 'd':4, 'e':5}"
1000000 loops, best of 3: 0.479 usec per loop

> python3.2 -m timeit "d = dict(a=1, b=2, c=3, d=4, e=5)"
1000000 loops, best of 3: 0.975 usec per loop

> python3.2 -m timeit "d = {'a':1, 'b':2, 'c':3, 'd':4, 'e':5}"
1000000 loops, best of 3: 0.409 usec per loop
Daniel Kluev
la source
10
@Ned: La plupart du temps pour la plupart des utilisateurs, cela n'a pas d'importance du tout, mais il y a des situations où des millions ou des milliards d'entre eux sont créés et une accélération 2x est significative.
M. Fooz
5
@MrFooz: il y a des situations comme ça. Je pense que vous constaterez que 99,9% des personnes qui effectuent des micro-horaires ne sont pas dans ces situations.
Ned Batchelder
29
@Ned C'est pertinent dans un fil qui demande lequel est le plus rapide.
Elliott
11
@Elliot L'OP n'a pas demandé lequel est le plus rapide.
Tom Ferguson
5
Si vous produisez soit des millions de dictés, soit un dict avec des millions de clés, à partir de littéraux dict dans votre source, vous vous trompez.
jwg
41

Ils sont à peu près les mêmes sur Python 3.2.

Comme l'a souligné gnibbler, le premier n'a pas besoin de chercher dict, ce qui devrait le rendre un peu plus rapide.

>>> def literal():
...   d = {'one': 1, 'two': 2}
...
>>> def constructor():
...   d = dict(one='1', two='2')
...
>>> import dis
>>> dis.dis(literal)
  2           0 BUILD_MAP                2
              3 LOAD_CONST               1 (1)
              6 LOAD_CONST               2 ('one')
              9 STORE_MAP
             10 LOAD_CONST               3 (2)
             13 LOAD_CONST               4 ('two')
             16 STORE_MAP
             17 STORE_FAST               0 (d)
             20 LOAD_CONST               0 (None)
             23 RETURN_VALUE
>>> dis.dis(constructor)
  2           0 LOAD_GLOBAL              0 (dict)
              3 LOAD_CONST               1 ('one')
              6 LOAD_CONST               2 ('1')
              9 LOAD_CONST               3 ('two')
             12 LOAD_CONST               4 ('2')
             15 CALL_FUNCTION          512
             18 STORE_FAST               0 (d)
             21 LOAD_CONST               0 (None)
             24 RETURN_VALUE
Paolo Moretti
la source
Notez que dans certaines implémentations, ce n'est pas vraiment un "petit morceau", plutôt un facteur de 100:$ pypy -m perf timeit -l '1000000' -n '5' -s 'i=(("a",1), ("b", 2), ("c", 3))' "{'a': 1, 'b': 2, 'c': 3}" ....... Mean +- std dev: 1.73 ns +- 0.14 ns $ pypy -m perf timeit -l '1000000' -n '5' -s 'i=(("a",1), ("b", 2), ("c", 3))' '{k:v for k,v in i}' ....... Mean +- std dev: 139 ns +- 10 ns $ pypy -m perf timeit -l '1000000' -n '5' -s 'i=(("a",1), ("b", 2), ("c", 3))' 'dict(i)' ....... Mean +- std dev: 188 ns +- 16 ns
DylanYoung
13

Ces deux approches produisent des dictionnaires identiques, sauf, comme vous l'avez noté, où les règles lexicales de Python interfèrent.

Les littéraux de dictionnaire sont un peu plus évidemment des dictionnaires, et vous pouvez créer n'importe quel type de clé, mais vous devez citer les noms des clés. D'un autre côté, vous pouvez utiliser des variables pour les clés si vous en avez besoin pour une raison quelconque:

a = "hello"
d = {
    a: 'hi'
    }

Le dict()constructeur vous donne plus de flexibilité en raison de la variété des formes d'entrée qu'il faut. Par exemple, vous pouvez lui fournir un itérateur de paires, et il les traitera comme des paires clé / valeur.

Je ne sais pas pourquoi PyCharm offrirait de convertir un formulaire en un autre.

Ned Batchelder
la source
2
Eh bien, je suppose que PyCharm essaie juste d'être très gentil. Tout comme il semble toujours offrir de convertir des chaînes entre guillemets simples en guillemets doubles - sans raison apparente.
maligree
1
Vous n'avez besoin de citer vos clés que si vos clés sont des chaînes. Ils pourraient tout aussi bien être des tuples de frozensets de flotteurs, bien que cela puisse devenir un peu moche.
Wooble
7

Une grande différence avec python 3.4 + pycharm est que le constructeur dict () génère un message "erreur de syntaxe" si le nombre de clés dépasse 256.

Je préfère utiliser le dict littéral maintenant.

Michel Boiron
la source
3
Ce n'est pas seulement python 3.4. Cela est dû au fait que CPython <3.7 a un nombre maximum de 255 arguments littéraux passés à un appelable. ( stackoverflow.com/a/8932175/2718295 )
cowbert
6

À partir du didacticiel python 2.7:

Une paire d'accolades crée un dictionnaire vide: {}. Placer une liste de paires clé: valeur séparées par des virgules dans les accolades ajoute les paires clé: valeur initiales au dictionnaire; c'est également ainsi que les dictionnaires sont écrits en sortie.

tel = {'jack': 4098, 'sape': 4139}
data = {k:v for k,v in zip(xrange(10), xrange(10,20))}

Tandis que:

Le constructeur dict () construit des dictionnaires directement à partir de listes de paires clé-valeur stockées sous forme de tuples. Lorsque les paires forment un modèle, les compréhensions de liste peuvent spécifier de manière compacte la liste de valeurs-clés.

tel = dict([('sape', 4139), ('guido', 4127), ('jack', 4098)]) {'sape': 4139, 'jack': 4098, 'guido': 4127}
data = dict((k,v) for k,v in zip(xrange(10), xrange(10,20)))

Lorsque les clés sont de simples chaînes, il est parfois plus facile de spécifier des paires à l'aide d'arguments de mots clés:

dict(sape=4139, guido=4127, jack=4098)
>>>  {'sape': 4139, 'jack':4098, 'guido': 4127}

Donc, {} et dict () produisent tous les deux un dictionnaire mais offrent des moyens un peu différents d'initialisation des données du dictionnaire.

Artsiom Rudzenka
la source
3

Je trouve le dict littéral d = {'one': '1'}beaucoup plus lisible, vos données de définition, plutôt que d'attribuer des valeurs et de les envoyer au dict()constructeur.

D'un autre côté, j'ai vu des gens mal saisir le littéral dict comme d = {'one', '1'}lequel en python moderne 2.7+ créerait un ensemble.

Malgré cela, je préfère toujours utiliser littéralement l'ensemble parce que je pense que sa préférence personnelle est plus lisible, je suppose.

lee penkman
la source
J'oublie régulièrement que la syntaxe littérale de sets existe. Je souhaite qu'il y ait une syntaxe littérale pour les dictés ordonnés ... je suis sûr que je les utilise plus souvent que les ensembles.
ArtOfWarfare
2

le littéral dict () est bien quand vous copiez des valeurs collées à partir d'autre chose (aucun python) Par exemple une liste de variables d'environnement. si vous aviez un fichier bash, dites

FOO='bar'
CABBAGE='good'

vous pouvez facilement coller ensuite dans un dict()littéral et ajouter des commentaires. Il est également plus facile de faire le contraire, de copier dans autre chose. Alors que la {'FOO': 'bar'}syntaxe est assez unique pour python et json. Donc, si vous utilisez beaucoup json, vous voudrez peut-être utiliser des {}littéraux avec des guillemets doubles.

Nick Humrich
la source
2

Il n'y a pas de littéral dict pour créer des classes héritées de dict, des classes dict personnalisées avec des méthodes supplémentaires. Dans ce cas, un constructeur de classe dict personnalisé doit être utilisé, par exemple:

class NestedDict(dict):

    # ... skipped

state_type_map = NestedDict(**{
    'owns': 'Another',
    'uses': 'Another',
})
Dmitriy Sintsov
la source
0

Tenez également compte du fait que les jetons qui correspondent aux opérateurs ne peuvent pas être utilisés dans la syntaxe du constructeur, c'est-à-dire les clés dasherisées.

>>> dict(foo-bar=1)
File "<stdin>", line 1
SyntaxError: keyword can't be an expression

>>> {'foo-bar': 1}
{'foo-bar': 1}
Brian Whitton
la source