Python Pardon vs Permission et Duck Typing

44

En Python, j'entends souvent dire qu'il est préférable de "demander pardon" (capture d'exception) au lieu de "demander l'autorisation" (vérification de type / condition). En ce qui concerne l'application de la frappe de canard en Python, est-ce que c'est

try:
    x = foo.bar
except AttributeError:
    pass
else:
    do(x)

mieux ou pire que

if hasattr(foo, "bar"):
    do(foo.bar)
else:
    pass

en termes de performances, de lisibilité, de "pythonique" ou de tout autre facteur important?

darkfeline
la source
17
il y a une troisième option, ne rien faire et traiter n'importe quel foo sans barre comme un bug
jk.
Je me souviens d’entendre que cela hasattrest mis en œuvre avec cet essai / interception exact en interne. Pas sûr que ce soit vrai ... (j'agirais différemment sur les propriétés, n'est-ce pas? getattr)
Izkata
@Izkata: L' implémentation dehasattr utilise l'équivalent C-API de getattr(retourne en Truecas de succès, Falsesinon), mais la gestion des exceptions en C est beaucoup plus rapide.
Martijn Pieters
2
J'ai accepté la réponse de Martijn, mais j'aimerais ajouter que si vous essayez de définir un attribut, vous devez absolument envisager d'utiliser try / catch, car il peut s'agir d'une propriété sans paramètre de définition, auquel cas hasattr sera vrai. , mais cela déclenchera toujours AttributeError.
darkfeline

Réponses:

60

Cela dépend vraiment de la fréquence à laquelle l’exception sera levée.

Les deux approches sont, à mon avis, également valables, du moins en termes de lisibilité et de pythonie. Mais si 90% de vos objets ne possèdent pas l'attribut, barvous remarquerez une différence de performance nette entre les deux approches:

>>> import timeit
>>> def askforgiveness(foo=object()):
...     try:
...         x = foo.bar
...     except AttributeError:
...         pass
... 
>>> def askpermission(foo=object()):
...     if hasattr(foo, 'bar'):
...         x = foo.bar
... 
>>> timeit.timeit('testfunc()', 'from __main__ import askforgiveness as testfunc')
2.9459929466247559
>>> timeit.timeit('testfunc()', 'from __main__ import askpermission as testfunc')
1.0396890640258789

Mais si 90% de vos objets n'ont l'attribut, les tables ont été transformées:

>>> class Foo(object):
...     bar = None
... 
>>> foo = Foo()
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askforgiveness as testfunc, foo')
0.31336188316345215
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askpermission as testfunc, foo')
0.4864199161529541

Du point de vue de la performance, vous devez donc choisir l’approche la mieux adaptée à votre situation.

En fin de compte, une utilisation stratégique du timeitmodule peut être la meilleure chose que vous puissiez faire en Pythonic.

Martijn Pieters
la source
1
Il y a quelques semaines, j'ai posé la question suivante: programmers.stackexchange.com/questions/161798/… Là-bas, je vous ai demandé si, dans un langage assez typé, vous deviez effectuer des vérifications de type supplémentaires et que des personnes m'avaient dit que ce n'était pas le cas. Sache que je vois que tu as.
Tulains Córdova
@ user1598390: Lorsque vous définissez une API qui attend un mélange homogène de types, vous devez effectuer certains tests. La plupart du temps, vous ne le faites pas. J'ai bien peur que ce soit un domaine spécifique sur lequel vous ne pouvez pas déduire de règles concernant les paradigmes dans leur ensemble.
Martijn Pieters
Tout développement système sérieux implique la définition d’une API. Donc, je suppose que les langages stricts sont les meilleurs pour cela car vous devez coder moins puisque le compilateur vérifie les types pour vous au moment de la compilation.
Tulains Córdova
1
@GarethRees: il établit un modèle pour la seconde moitié de la réponse lorsque je passe un argument à la fonction à tester.
Martijn Pieters
1
Notez que hasattrcela correspond en fait à l'équivalent C-api d'un essai - sauf sous le capot de toute façon, puisqu'il s'avère que le seul moyen général de déterminer si un objet a un attribut dans Python consiste à essayer d'y accéder.
user2357112 prend en charge Monica
11

En python, vous obtenez souvent de meilleures performances lorsque vous faites les choses à la manière de Python. Avec d'autres langages, l'utilisation d'exceptions pour le contrôle de flux est généralement considérée comme une idée terrible car les exceptions imposent généralement une surcharge extraordinaire. Mais comme cette technique est explicitement approuvée en Python, l'interpréteur est optimisé pour ce type de code.

Comme pour toutes les questions relatives aux performances, la seule façon d’en être certain est de profiler votre code. Écrivez les deux versions et voyez laquelle est la plus rapide. Selon mon expérience, la "méthode Python" est généralement la méthode la plus rapide.

tylerl
la source
3

Je pense que la performance est une préoccupation secondaire. Si cela se produit, un profileur vous aidera à vous concentrer sur les véritables goulots d'étranglement, ce qui peut être ou ne pas être la façon dont vous traitez les éventuels arguments illégaux.

La lisibilité et la simplicité, par contre, sont toujours une préoccupation majeure. Il n'y a pas de règles strictes ici, utilisez simplement votre jugement.

C'est un problème universel, mais les conventions spécifiques à l'environnement ou à la langue sont pertinentes. Par exemple, en Python, il est généralement préférable d’utiliser simplement l’attribut attendu et de laisser un éventuel AttributeError atteindre l’appelant.

orip
la source
-1

Pour ce qui est de l' exactitude , je pense que la gestion des exceptions est la voie à suivre (j'utilise parfois l'approche hasattr () moi-même, cependant). Le problème fondamental de hasattr () est qu’il transforme les violations de contrats de code en échecs silencieux (c’est un gros problème en JavaScript, qui ne jette pas sur des propriétés non existantes).

Joe
la source
3
Votre réponse ne semble pas ajouter grand chose à ce qui a déjà été dit par d’autres. Contrairement aux autres sites, les programmeurs attendent des réponses expliquant le pourquoi de la réponse. Vous abordez un bon point avec la question de l'échec silencieux, mais vous ne devez pas en rendre justice. La lecture de ce qui suit peut aider: programmers.stackexchange.com/help/how-to-answer
La dernière réponse que j'ai faite a été critiquée pour être trop large. Je pensais que j'essayerais court et succinct.
Joe