web-dev-qa-db-fra.com

Dictionnaires et valeurs par défaut

En supposant que connectionDetails soit un dictionnaire Python, quel est le meilleur moyen, le plus élégant et le plus "pythonique" de refactoriser du code comme celui-ci?

if "Host" in connectionDetails:
    Host = connectionDetails["Host"]
else:
    Host = someDefaultValue
169
mnowotka

Comme ça:

Host = connectionDetails.get('Host', someDefaultValue)
254
MattH

Vous pouvez également utiliser le defaultdict like so:

from collections import defaultdict
a = defaultdict(lambda: "default", key="some_value")
a["blabla"] => "default"
a["key"] => "some_value"

Vous pouvez passer n'importe quelle fonction ordinaire à la place de lambda:

from collections import defaultdict
def a():
  return 4

b = defaultdict(a, key="some_value")
b['absent'] => 4
b['key'] => "some_value"
84
tamerlaha

Si .get() est un idiome de Nice, il est plus lent que if/else (et plus lent que try/except si la présence de la clé dans le dictionnaire est prévisible la plupart du temps):

>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.07691968797894333
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.4583777282275605
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(1, 10)")
0.17784020746671558
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(2, 10)")
0.17952161730158878
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.10071221458065338
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.06966537335119938
22
Tim Pietzcker

Pour plusieurs valeurs par défaut différentes, essayez ceci:

connectionDetails = { "Host": "www.example.com" }
defaults = { "Host": "127.0.0.1", "port": 8080 }

completeDetails = {}
completeDetails.update(defaults)
completeDetails.update(connectionDetails)
completeDetails["Host"]  # ==> "www.example.com"
completeDetails["port"]  # ==> 8080
17
Jerome Baum

Il existe une méthode dans les dictionnaires python pour le faire: dict.setdefault

connectionDetails.setdefault('Host',someDefaultValue)
Host = connectionDetails['Host']

Cependant, cette méthode définit la valeur de connectionDetails['Host'] sur someDefaultValue si la clé Host n'est pas déjà définie, contrairement à ce que la question a demandé.

7
Sriram

(c'est une réponse tardive)

Une alternative consiste à sous-classer la classe dict et à implémenter la méthode __missing__() , comme ceci:

class ConnectionDetails(dict):
    def __missing__(self, key):
        if key == 'Host':
            return "localhost"
        raise KeyError(key)

Exemples:

>>> connection_details = ConnectionDetails(port=80)

>>> connection_details['Host']
'localhost'

>>> connection_details['port']
80

>>> connection_details['password']
Traceback (most recent call last):
  File "python", line 1, in <module>
  File "python", line 6, in __missing__
KeyError: 'password'
5
Laurent LAPORTE

En testant les soupçons de @Tim Pietzcker sur la situation dans PyPy (5.2.0-alpha0) pour Python 3.3.5, je constate que les fonctions .get() et if/else fonctionnent de manière similaire. En fait, il semble que dans le cas où/d'autre il n'y a même qu'une seule recherche si la condition et l'affectation impliquent la même clé (comparez avec le dernier cas où il y a deux recherches).

>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.011889292989508249
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.07310474599944428
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(1, 10)")
0.010391917996457778
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(2, 10)")
0.009348208011942916
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.011475925013655797
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.009605801998986863
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=d[1]")
0.017342638995614834
2
Till

Vous pouvez utiliser une fonction lamba pour cela comme une ligne. Créez un nouvel objet connectionDetails2 auquel on accède comme une fonction ...

connectionDetails2 = lambda k: connectionDetails[k] if k in connectionDetails.keys() else "DEFAULT"

Maintenant utiliser 

connectionDetails2(k)

au lieu de 

connectionDetails[k]

qui retourne la valeur du dictionnaire si k est dans les clés, sinon il retourne "DEFAULT"

0
Bobak Hashemi