web-dev-qa-db-fra.com

Quelle est la meilleure façon de vérifier l'existence d'un attribut?

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?

61
Andrew Halloran

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.

La mauvaise direction

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.

La voie EAFP

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 voie LBYL

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.

La manière getattr

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

Conclusion

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.)

139
Dietrich Epp

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é.

11
jfs

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.

5
iTayb

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
0
Mikael Lindlöf