web-dev-qa-db-fra.com

Python: `si clé dans dict` contre` try/except` - quel est le langage le plus lisible?

J'ai une question sur les idiomes et la lisibilité, et il semble y avoir un choc des philosophies Python pour ce cas particulier:

Je souhaite créer le dictionnaire A à partir du dictionnaire B. Si une clé spécifique n'existe pas dans B, ne faites rien et continuez.

Quel est le meilleur chemin?

try:
    A["blah"] = B["blah"]
except KeyError:
    pass

ou 

if "blah" in B:
    A["blah"] = B["blah"]

"Faites et demandez pardon" vs "simplicité et explicite".

Quel est le meilleur et pourquoi?

77
LeeMobile

Les exceptions ne sont pas conditionnelles.

La version conditionnelle est plus claire. C'est naturel: il s'agit d'un contrôle de flux simple, pour lequel les conditions sont conçues, pas d'exceptions.

La version d'exception est principalement utilisée comme optimisation lors de ces recherches dans une boucle: elle permet, pour certains algorithmes, d'éliminer les tests des boucles internes. Il n'a pas cet avantage ici. Cela a le petit avantage d’éviter de dire "blah" deux fois, mais si vous en faites beaucoup, vous devriez probablement avoir une fonction auxiliaire move_key de toute façon.

En général, je vous recommande fortement de vous en tenir à la version conditionnelle par défaut, sauf si vous avez une raison particulière de ne pas le faire. Les conditions sont le moyen évident de le faire, ce qui est généralement une recommandation forte de préférer une solution à une autre.

60
Glenn Maynard

Il existe également une troisième méthode qui évite les exceptions et la double recherche, ce qui peut être important si la recherche est coûteuse:

value = B.get("blah", None)
if value is None: 
    A["blah"] = value

Si vous pensez que le dictionnaire contient des valeurs None, vous pouvez utiliser des constantes plus ésotériques telles que NotImplemented, Ellipsis ou en créer une nouvelle:

MyConst = object()
def update_key(A, B, key):
    value = B.get(key, MyConst)
    if value is not MyConst: 
        A[key] = value

Quoi qu'il en soit, utiliser update() est l'option la plus lisible pour moi:

a.update((k, b[k]) for k in ("foo", "bar", "blah") if k in b)
45
lqc

D'après ce que j'ai compris, vous voulez mettre à jour le dictateur A avec les paires clé/valeur de dict B

update est un meilleur choix.

A.update(B)

Exemple:

>>> A = {'a':1, 'b': 2, 'c':3}
>>> B = {'d': 2, 'b':5, 'c': 4}
>>> A.update(B)
>>> A
{'a': 1, 'c': 4, 'b': 5, 'd': 2}
>>> 
14
pyfunc

Citation directe du wiki de performance Python:

À l'exception de la première fois, chaque fois qu'un mot est vu, le test de l'instruction if échoue. Si vous comptez un grand nombre de mots, ceux-ci seront probablement répétés plusieurs fois. Dans une situation où l'initialisation d'une valeur ne se produira qu'une seule fois et que l'augmentation de cette valeur se produira plusieurs fois, il est moins coûteux d'utiliser une instruction try.

Il semble donc que les deux options sont viables selon la situation. Pour plus de détails, vous pouvez consulter ce lien: Try-except-performance

6
Sami Lehtinen

Je pense que la règle générale est la suivante: A["blah"] existe-t-il normalement? Si c'est le cas, sauf si c'est bon, sinon utilisez if "blah" in b:

Je pense que "essayer" n'est pas cher dans le temps mais "sauf" est plus cher.

3
neil

Je pense que le deuxième exemple est ce que vous devriez choisir sauf si ce code a du sens

try:
    A["foo"] = B["foo"]
    A["bar"] = B["bar"]
    A["baz"] = B["baz"]
except KeyError:
    pass

Gardez à l'esprit que le code sera abandonné dès qu'il y a une clé qui n'est pas dans B. Si ce code a du sens, vous devriez alors utiliser la méthode d'exception, sinon utilisez la méthode de test. À mon avis, comme il est plus court et exprime clairement l'intention, il est beaucoup plus facile à lire que la méthode des exceptions.

Bien sûr, les personnes qui vous disent d’utiliser update sont correctes. Si vous utilisez une version de Python qui prend en charge la compréhension du dictionnaire, je préférerais fortement ce code:

updateset = {'foo', 'bar', 'baz'}
A.update({k: B[k] for k in updateset if k in B})
3
Omnifarious

La règle dans d’autres langues est de réserver des exceptions pour des conditions exceptionnelles, c’est-à-dire des erreurs qui ne se produisent pas en utilisation normale. Je ne sais pas comment cette règle s'applique à Python, car StopIteration ne devrait pas exister avec cette règle.

2
Mark Ransom

Personnellement, je me penche vers la deuxième méthode (mais en utilisant has_key):

if B.has_key("blah"):
  A["blah"] = B["blah"]

De cette façon, chaque opération d’affectation ne comporte que deux lignes (au lieu de 4 avec try/except), et toutes les exceptions renvoyées sont de véritables erreurs ou des erreurs que vous avez manquées (au lieu d’essayer d’accéder à des clés absentes). .

Il se trouve que (voir les commentaires sur votre question), has_key est obsolète - donc je suppose que c'est mieux écrit:

if "blah" in B:
  A["blah"] = B["blah"]
1
girasquid

Pourquoi ne pas simplement faire ceci:

def try_except(x,col):
    try:
        return x[col]
    except:
        return None

list(map(lambda x: try_except(x,'blah'),A))
0
Nico Coallier

À partir de Python 3.8 et de l'introduction des expressions d'affectation (PEP 572) (opérateur :=), nous pouvons capturer la valeur de la condition dictB.get('hello', None) dans une variable value afin de vérifier si ce n'est pas None (car dict.get('hello', None) renvoie la valeur associée ou None) et ensuite l'utiliser dans le corps de la condition:

# dictB = {'hello': 5, 'world': 42}
# dictA = {}
if value := dictB.get('hello', None):
  dictA["hello"] = value
# dictA is now {'hello': 5}
0
Xavier Guihot