2012-08-15 17 views
22
class A(): pass 

a = A() 
b = A() 

a.b = b 
b.c = 1 

a.b  # this is b 
getattr(a, "b") # so is this 

a.b.c # this is 1 
getattr(a, "b.c") # this raises an AttributeError 

Parecía muy natural para mí suponer esto último. Estoy seguro de que hay una buena razón para esto. ¿Qué es?¿Por qué `getattr` no admite recuperaciones consecutivas de atributos?

+4

Ahora hacer esto: 'setattr (a, 'b.c', 2')'. ¿Qué debería 'getattr (a,' b.c ') 'devolver ahora? ¿Qué pasaría si no hubiera 'c' en' b' antes? Puede usar un '.' en los nombres de los atributos, por lo que no puede esperar que' getattr' pueda atravesar objetos como este. –

Respuesta

30

No se puede poner un punto en la función getattr porque getattr es una búsqueda directa en el diccionario del objeto.

Si usa la función 'dir' en a, verá las claves del diccionario que corresponden a los atributos de su objeto. En este caso, "b.c" no es en el conjunto de claves del diccionario.

La única manera de hacer esto es con getattr anidar llamadas:

getattr(getattr(a, "b"), "c") 

Por suerte, la biblioteca estándar tiene una mejor solución!

import operator 
operator.attrgetter("b.c")(a) 
+7

El OP se ha dado cuenta de que ... Creo que está preguntando por qué. No es una pregunta irracional dado que operator.attrgetter ('a.b') (obj) _will_ resolverá notación punteada –

+0

Gracias por señalarlo. Aclareé mi respuesta para poder explicar mejor lo que quise decir. –

+0

Esta es una solución muy buena y simple para un problema problemático. Las respuestas a continuación no son tan concisas. Esta respuesta no intenta implementar algo que Python ya hizo, lo que lo hace bueno. – Bobort

6

Porque getattr no funciona de esa manera. getattr obtiene el atributo de un objeto dado (primer argumento) con un nombre dado (segundo argumento). Por lo que su código:

getattr(a, "b.c") # this raises an AttributeError 

significa: acceso "b.c" atributo del objeto referenciado por "a". Obviamente, su objeto no tiene un atributo llamado "b.c".

Para conseguir "c" atributo que debe utilizar dos getattr llamadas:

getattr(getattr(a, "b"), "c") 

Vamos desenvolverlo para una mejor comprensión:

b = getattr(a, "b") 
c = getattr(b, "c") 
0

Lo que debería volver getattr('a.b', {'a': None}, 'default-value'}? ¿Debería aumentar AttributeError o simplemente devolver 'default-value'? Es por eso que las claves complejas si se introducen en getattr lo harían difícil de usar.

Por lo tanto, es más natural ver getattr(..) funcionar como get método del diccionario de atributos de objeto.

4

Creo que la confusión surge del hecho de que la notación de punto recto (ex a.b.c) tiene los mismos parámetros que getattr(), pero la lógica de análisis es diferente. Si bien ambos son esenciales para el atributo __dict__ de un objeto, getattr() no está sujeto a los requisitos más estrictos sobre los atributos accesibles por punto. Por ejemplo

setattr(foo, 'Big fat ugly string. But you can hash it.', 2) 

es válida, ya que la cadena sólo se convierte en una clave hash en foo.__dict__, pero

foo.Big fat ugly string. But you can hash it. = 2 

y

foo.'Big fat ugly string. But you can hash it.' = 2 

son errores de sintaxis, porque ahora se está pidiendo que el intérprete analizar estas cosas como código sin formato, y eso no funciona.

La otra cara de esto es que mientras foo.b.c es equivalente a foo.__dict__['b'].__dict__['c'], getattr(foo, 'b.c') es equivalente a foo.__dict__['b.c']. Es por eso que getattr no funciona como esperaba.

+1

Gosh, veo que esto sucede en un concurso de códigos ofuscado ... –

37

Python's built-in reduce function habilita la funcionalidad que está buscando. Aquí hay una pequeña función auxiliar que hará el trabajo:

class NoDefaultProvided(object): 
    pass 

def getattrd(obj, name, default=NoDefaultProvided): 
    """ 
    Same as getattr(), but allows dot notation lookup 
    Discussed in: 
    http://stackoverflow.com/questions/11975781 
    """ 

    try: 
     return reduce(getattr, name.split("."), obj) 
    except AttributeError, e: 
     if default != NoDefaultProvided: 
      return default 
     raise 

Prueba de prueba;

>>> getattrd(int, 'a') 
AttributeError: type object 'int' has no attribute 'a' 

>>> getattr(int, 'a') 
AttributeError: type object 'int' has no attribute 'a' 

>>> getattrd(int, 'a', None) 
None 

>>> getattr(int, 'a', None) 
None 

>>> getattrd(int, 'a', None) 
None 

>>> getattrd(int, '__class__.__name__') 
type 

>>> getattrd(int, '__class__') 
<type 'type'> 
+3

Esta debería ser la respuesta aceptada, la respuesta de Thane es inútil. – sleepycal

+0

Estoy a favor de una buena respuesta, y esto hace el trabajo. Pero esta respuesta no le da al OP lo que estaba buscando, el * motivo por el cual * no funciona. –

+0

La respuesta aceptada no resuelve el problema original. Suponiendo que solo le den la cadena ''b.c'', esta es la única respuesta que resuelve el problema. –

0

Puede llamar al getattr múltiple sin llamar a una función dentro de la función mediante el fraccionamiento de los operadores de punto y realizar una getattr() para cada operador punto

def multi_getattr(self,obj, attr, default = None): 
      attributes = attr.split(".") 
      for i in attributes: 
       try: 
        obj = getattr(obj, i) 
       except AttributeError: 
        if default: 
         return default 
        else: 
         raise 
      return obj 

Si supongamos que desea llamar ABCD se puede hazlo a través de a.multi_getattr ('bcd'). Esto generalizará la operación sin preocuparse por el recuento de la operación de punto que uno tiene en la cadena.

2

Creo que la forma más directa de lograr lo que desea es usar operator.attrgetter.

>>> import operator 
>>> class B(): 
... c = 'foo' 
... 
>>> class A(): 
... b = B() 
... 
>>> a = A() 
>>> operator.attrgetter('b.c')(a) 
'foo' 

Si el atributo no existe, entonces obtendrá un AttributeError

>>> operator.attrgetter('b.d')(a) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: B instance has no attribute 'd' 
Cuestiones relacionadas