2012-03-17 24 views
54

¿Cuál es una mejor manera de verificar la existencia de un atributo?¿Cuál es la mejor manera de verificar la existencia de un atributo?

Jarret Hardie proporcionan esta respuesta:

if hasattr(a, 'property'): 
    a.property 

veo que también se puede hacer de esta manera:

if 'property' in a.__dict__: 
    a.property 

es un enfoque suele utilizar más que otros?

+0

La segunda opción es malo, de todos modos la [segunda respuesta] (http://stackoverflow.com/a/610923/1132524) desde el enlace que ya ha proporcionado respuesta a su pregunta. –

Respuesta

124

No existe la "mejor" manera, porque nunca se está simplemente comprobando si existe un atributo; siempre es parte de un programa más grande. Hay varias formas correctas y una notable manera incorrecta.

El camino equivocado

if 'property' in a.__dict__: 
    a.property 

Aquí es una demostración que muestra esta técnica en su defecto:

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 

Salida:

 
'prop' in a.__dict__ = False 
hasattr(a, 'prop') = True 
a.prop = 3 

mayor parte del tiempo, que no quieren meterse con __dict__. Es un atributo especial para hacer cosas especiales, y verificar si existe un atributo es bastante mundano.

El EAFP manera

Un idioma común en Python es "más fácil pedir perdón que pedir permiso", o EAFP para abreviar. Verá un montón de código Python que usa este modismo, y no solo para verificar la existencia de atributos.

# 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() 

Tenga en cuenta que esta es exactamente la misma expresión idiomática para abrir un archivo que puede no existir.

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 

Además, para convertir cadenas en números enteros.

try: 
    i = int(s) 
except ValueError: 
    print "Not an integer! Please try again." 
    sys.exit(1) 

Incluso la importación de módulos opcionales ...

try: 
    import readline 
except ImportError: 
    pass 

La forma LBYL

El método hasattr, por supuesto, también funciona. Esta técnica se llama "mirar antes de saltar", o LBYL para abreviar.

# Cached attribute 
if not hasattr(self, 'big_object'): 
    big_object = CreateBigObject() 
    self.big_object = CreateBigObject() 
big_object.do_something() 

(La orden interna hasattr realidad comporta de forma extraña en las versiones de Python anteriores a 3.2 en relación con excepciones - que capturar las excepciones que no debería -. Pero esto es probablemente irrelevante, ya que es poco probable que tales excepciones El La técnica hasattr también es más lenta que try/except, pero no la llama con la frecuencia suficiente para importar y la diferencia no es muy grande. Finalmente, hasattr no es atómico, por lo que podría arrojar AttributeError si otro hilo elimina el atributo, pero esto es un escenario exagerado y deberá tener mucho cuidado con los hilos de todos modos. No creo que valga la pena preocuparse por ninguna de estas tres diferencias.)

El uso de hasattr es mucho más simple que try/except, siempre que lo único que necesite saber es si el atributo existe. El gran problema para mí es que la técnica LBYL parece "extraña", ya que como programador de Python estoy más acostumbrado a leer la técnica EAFP. Si reescribe los ejemplos anteriores para que utilicen el estilo LBYL, obtendrá un código que es torpe, francamente incorrecto o demasiado difícil de escribir.

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

Y LBYL veces es pura y simple incorrecto:

if os.path.isfile('some_file'): 
    # At this point, some other program could 
    # delete some_file... 
    f = open('some_file', 'r') 

Si desea escribir una función LBYL para la importación de módulos opcionales, adelante ... que suena como la función sería todo un monstruo .

La forma getattr

Si sólo tiene un valor por defecto, getattr es una versión más corta de try/except.

x = getattr(self, 'x', default_value) 

Si el valor predeterminado es caro de construir, entonces usted va a terminar con algo como esto:

x = getattr(self, 'attr', None) 
if x is None: 
    x = CreateDefaultValue() 
    self.attr = x 

O si None es un valor posible,

sentinel = object() 

x = getattr(self, 'attr', sentinel) 
if x is sentinel: 
    x = CreateDefaultValue() 
    self.attr = x 

Conclusión

Internamente, getattr y hasattr builtins solo use la técnica try/except (excepto lo escrito en C). Entonces todos se comportan de la misma manera en lo que cuenta, y elegir el correcto se debe a una cuestión de circunstancias y estilo.

El código EAFP try/except siempre frotará algunos programadores de la manera incorrecta, y el código hasattr/getattr LBYL irritará a otros programadores. Ambos son correctos, y a menudo no hay una razón realmente convincente para elegir uno o el otro. (Sin embargo, otros programadores están disgustados que usted consideraría normal que un atributo a ser indefinido, y algunos programadores están horrorizados de que es posible tener un atributo definido en Python.)

+4

Posiblemente el 'try ... except' es mejor cuando casi siempre se espera que' x' tenga el atributo 'x.attr'. (Entonces la excepción sería realmente excepcional.) –

+3

Casi siempre uso 'try ... except' personalmente, el método' hasattr' deja un mal sabor de boca. –

+0

@Dietrich Epp: el código que podría ocultar una excepción como 'try/except' anterior es definitivamente peor que' hasattr() '. – jfs

10

hasattr() es la forma *.

a.__dict__ es feo y no funciona en muchos casos. hasattr() realmente intenta obtener el atributo y captura AttributeError internamente por lo que funciona incluso si define el método personalizado __getattr__().

Para evitar que solicita el atributo dos veces el tercer argumento para getattr() podría utilizarse:

not_exist = object() 

# ... 
attr = getattr(obj, 'attr', not_exist) 
if attr is not_exist: 
    do_something_else() 
else: 
    do_something(attr) 

Se podía usar un valor por defecto en lugar de not_exist centinela si es más apropiado en su caso.

No me gusta try: do_something(x.attr) \n except AttributeError: .. podría ocultar AttributeError dentro de la función do_something().

*Before Python 3.1 hasattr() suppressed all exceptions (no sólo AttributeError) si no es deseable getattr() debe ser utilizado.

4

hasattr() es la manera Ptónica de hacerlo. Aprende, ámalo.

Otra manera posible es comprobar si el nombre de la variable está en locals() o globals():

if varName in locals() or in globals(): 
    do_something() 
else: 
    do_something_else() 

Yo personalmente odio a capturar las excepciones con el fin de comprobar algo. Se ve y se siente feo. Es idéntica a comprobar si una cadena contiene sólo dígitos de esa manera:

s = "84984x" 
try: 
    int(s) 
    do_something(s) 
except ValueError: 
    do_something_else(s) 

En lugar de suavidad utilizando s.isdigit(). Eww.

+0

Las versiones 'int' /' isdigit' difieren en una forma importante; la versión 'int' permite números negativos. Te reto a que encuentres un equivalente conciso y legible a 'try: int (s) excepto ValueError: ...' que funciona correctamente para números negativos, y rechaza números con ceros iniciales extraños (como lo hace Python). –

0

Pregunta muy antigua pero realmente necesita una buena respuesta. ¡Incluso para un programa corto, diría que utilizo una función personalizada!

Aquí hay un ejemplo. No es perfecto para todas las aplicaciones, pero sí lo es para mí, para analizar respuestas de incontables API y utilizar Django. Es fácil de arreglar para los propios requisitos de cada uno.

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 
Cuestiones relacionadas