Quelle est la meilleure façon de vérifier l'existence d'un attribut?
Jarret Hardie a fourni cette réponse:
if hasattr(a, 'property'):
a.property
Je vois que cela peut aussi se faire de cette façon:
if 'property' in a.__dict__:
a.property
Une approche est-elle généralement utilisée plus que d'autres?
Il n'y a pas de "meilleure" façon), == parce que vous ne vérifiez jamais simplement si un attribut existe; cela fait toujours partie d'un programme plus vaste. Il existe plusieurs façons correctes et une manière incorrecte notable.
if 'property' in a.__dict__:
a.property
Voici une démonstration qui montre l'échec de cette technique:
class A(object):
@property
def prop(self):
return 3
a = A()
print "'prop' in a.__dict__ =", 'prop' in a.__dict__
print "hasattr(a, 'prop') =", hasattr(a, 'prop')
print "a.prop =", a.prop
Sortie:
'prop' dans un .__ dict__ = Faux hasattr (a, 'prop' Vrai a.prop = 3
La plupart du temps, vous ne voulez pas jouer avec __dict__
. C'est un attribut spécial pour faire des choses spéciales, et vérifier si un attribut existe est assez banal.
Un idiome courant dans Python est "plus facile de demander pardon que permission", ou EAFP pour faire court. Vous verrez beaucoup de Python code qui utilise cet idiome , et pas seulement pour vérifier l'existence d'attributs.
# Cached attribute
try:
big_object = self.big_object
# or getattr(self, 'big_object')
except AttributeError:
# Creating the Big Object takes five days
# and three hundred pounds of over-ripe melons.
big_object = CreateBigObject()
self.big_object = big_object
big_object.do_something()
Notez qu'il s'agit exactement du même idiome pour l'ouverture d'un fichier qui peut ne pas exister.
try:
f = open('some_file', 'r')
except IOError as ex:
if ex.errno != errno.ENOENT:
raise
# it doesn't exist
else:
# it does and it's open
Aussi, pour convertir des chaînes en entiers.
try:
i = int(s)
except ValueError:
print "Not an integer! Please try again."
sys.exit(1)
Même l'importation de modules optionnels ...
try:
import readline
except ImportError:
pass
La méthode hasattr
, bien sûr, fonctionne aussi. Cette technique est appelée "regardez avant de sauter", ou LBYL pour faire court.
# Cached attribute
if not hasattr(self, 'big_object'):
big_object = CreateBigObject()
self.big_object = CreateBigObject()
big_object.do_something()
(Le programme intégré hasattr
se comporte en fait étrangement dans les versions Python antérieures à 3.2 en ce qui concerne les exceptions - il interceptera les exceptions qu'il ne devrait pas - mais cela n'est probablement pas pertinent, car de telles exceptions sont peu probables. La technique hasattr
est également plus lente que try/except
, mais vous n'appelez pas cela assez souvent pour vous en soucier et la différence n'est pas très grande. Enfin, hasattr
n'est pas atomique, il pourrait donc lancer AttributeError
si un autre thread supprime l'attribut, mais c'est un scénario farfelu et vous devrez de toute façon être très prudent avec les threads. Je ne pense pas que ces trois différences méritent d'être inquiétées.)
Utiliser hasattr
est beaucoup plus simple que try/except
, tant que tout ce que vous devez savoir est de savoir si l'attribut existe. Le gros problème pour moi est que la technique LBYL semble "étrange", car en tant que programmeur Python je suis plus habitué à lire la technique EAFP. Si vous réécrivez les exemples ci-dessus pour qu'ils utilisent le style LBYL
, vous obtenez du code qui est soit maladroit, carrément incorrect, soit trop difficile à écrire.
# Seems rather fragile...
if re.match('^(:?0|-?[1-9][0-9]*)$', s):
i = int(s)
else:
print "Not an integer! Please try again."
sys.exit(1)
Et LBYL est parfois carrément incorrect:
if os.path.isfile('some_file'):
# At this point, some other program could
# delete some_file...
f = open('some_file', 'r')
Si vous voulez écrire une fonction LBYL pour importer des modules optionnels, soyez mon invité ... on dirait que la fonction serait un monstre total.
Si vous avez juste besoin d'une valeur par défaut, getattr
est une version plus courte de try/except
.
x = getattr(self, 'x', default_value)
Si la valeur par défaut est coûteuse à construire, vous vous retrouverez avec quelque chose comme ceci:
x = getattr(self, 'attr', None)
if x is None:
x = CreateDefaultValue()
self.attr = x
Ou si None
est une valeur possible,
sentinel = object()
x = getattr(self, 'attr', sentinel)
if x is sentinel:
x = CreateDefaultValue()
self.attr = x
En interne, les commandes internes getattr
et hasattr
utilisent simplement try/except
technique (sauf écrit en C). Ils se comportent donc tous de la même manière là où cela compte, et choisir la bonne est une question de circonstances et de style.
Le try/except
Le code EAFP effacera toujours certains programmeurs dans le mauvais sens, et le hasattr/getattr
Le code LBYL irrite les autres programmeurs. Ils sont tous les deux corrects, et il n'y a souvent aucune raison vraiment convaincante de choisir l'un ou l'autre. (Pourtant, d'autres programmeurs sont dégoûtés de considérer qu'il est normal qu'un attribut ne soit pas défini, et certains programmeurs sont horrifiés qu'il soit même possible d'avoir un attribut non défini en Python.)
hasattr()
est le moyen*.
a.__dict__
Est moche et ne fonctionne pas dans de nombreux cas. hasattr()
essaie en fait d'obtenir l'attribut et attrape AttributeError
en interne, donc cela fonctionne même si vous définissez la méthode personnalisée __getattr__()
.
Pour éviter de demander deux fois l'attribut, le troisième argument de getattr()
pourrait être utilisé:
not_exist = object()
# ...
attr = getattr(obj, 'attr', not_exist)
if attr is not_exist:
do_something_else()
else:
do_something(attr)
Vous pouvez simplement utiliser une valeur par défaut au lieu de not_exist
Sentinelle si cela est plus approprié dans votre cas.
Je n'aime pas try: do_something(x.attr) \n except AttributeError: ..
cela pourrait cacher AttributeError
à l'intérieur de la fonction do_something()
.
*Avant Python 3.1 hasattr()
supprimé toutes les exceptions (pas seulement AttributeError
) si ce n'est pas souhaitable getattr()
Devrait être utilisé.
hasattr()
est la façon Pythonique de le faire. Apprenez-le, aimez-le.
Une autre manière possible est de vérifier si le nom de la variable est dans locals()
ou globals()
:
if varName in locals() or in globals():
do_something()
else:
do_something_else()
Personnellement, je déteste attraper des exceptions afin de vérifier quelque chose. Il a l'air et se sent laid. C'est identique à vérifier si une chaîne ne contient que des chiffres de cette façon:
s = "84984x"
try:
int(s)
do_something(s)
except ValueError:
do_something_else(s)
Au lieu d'utiliser doucement s.isdigit()
. Eww.
Très vieille question mais elle a vraiment besoin d'une bonne réponse. Pour même un programme court, je dirais utiliser une fonction personnalisée!
Voici un exemple. Ce n'est pas parfait pour toutes les applications mais c'est pour le mien, pour analyser les réponses d'innombrables API et utiliser Django. Il est facile à corriger pour les besoins de chacun.
from Django.core.exceptions import ObjectDoesNotExist
from functools import reduce
class MultipleObjectsReturned(Exception):
pass
def get_attr(obj, attr, default, asString=False, silent=True):
"""
Gets any attribute of obj.
Recursively get attributes by separating attribute names with the .-character.
Calls the last attribute if it's a function.
Usage: get_attr(obj, 'x.y.z', None)
"""
try:
attr = reduce(getattr, attr.split("."), obj)
if hasattr(attr, '__call__'):
attr = attr()
if attr is None:
return default
if isinstance(attr, list):
if len(attr) > 1:
logger.debug("Found multiple attributes: " + str(attr))
raise MultipleObjectsReturned("Expected a single attribute")
else:
return str(attr[0]) if asString else attr[0]
else:
return str(attr) if asString else attr
except AttributeError:
if not silent:
raise
return default
except ObjectDoesNotExist:
if not silent:
raise
return default