2011-12-16 12 views
11

Supongamos que tenemos la siguiente jerarquía de clases:¿Hay alguna manera de acceder a __dict__ (o algo así) que incluya clases base?

class ClassA: 

    @property 
    def foo(self): return "hello" 

class ClassB(ClassA): 

    @property 
    def bar(self): return "world" 

Si exploro __ dict __ en ClassB como tal, sólo veo el atributo de barras:

for name,_ in ClassB.__dict__.items(): 

    if name.startswith("__"): 
     continue 

    print(name) 

salida es la barra

Puedo utilizar mis propios medios para obtener atributos no solo del tipo especificado sino de sus antepasados. Sin embargo, mi pregunta es si ya existe una forma en Python para que pueda hacer esto sin reinventar una rueda.

def return_attributes_including_inherited(type): 
    results = [] 
    return_attributes_including_inherited_helper(type,results) 
    return results 

def return_attributes_including_inherited_helper(type,attributes): 

    for name,attribute_as_object in type.__dict__.items(): 

     if name.startswith("__"): 
      continue 

     attributes.append(name) 

    for base_type in type.__bases__: 
     return_attributes_including_inherited_helper(base_type,attributes) 

Ejecución de mi código de la siguiente manera ...

for attribute_name in return_attributes_including_inherited(ClassB): 
    print(attribute_name) 

... da la espalda tanto bar y foo.

Nota que estoy simplificando algunas cosas: conflictos de nombres, el uso de elementos() cuando para este ejemplo que podría utilizar dict, pasando por alto cualquier cosa que empieza con __, haciendo caso omiso de la posibilidad de que dos antepasados ​​mismos tienen un ancestro común, etc.

EDIT1 - Intenté mantener el ejemplo simple. Pero realmente quiero tanto el nombre del atributo como la referencia del atributo para cada clase y clase antecesora. Una de las respuestas a continuación me ayuda a mejorar, publicaré un código mejor cuando lo haga funcionar.

EDIT2 - Esto hace lo que quiero y es muy breve. Se basa en la respuesta de Eli a continuación.

def get_attributes(type): 

    attributes = set(type.__dict__.items()) 

    for type in type.__mro__: 
     attributes.update(type.__dict__.items()) 

    return attributes 

Devuelve los nombres de los atributos y sus referencias.

EDIT3 - Una de las respuestas siguientes sugiere el uso de inspect.getmembers. Esto parece muy útil porque es como Dict, pero también opera en clases de antepasados.

Dado que una gran parte de lo que yo estaba tratando de hacer era encontrar atributos marcados con un descriptor en particular, e incluyen clases antepasados, aquí hay un código que ayudaría a hacer que, en caso de que ayuda a nadie:

class MyCustomDescriptor: 

    # This is greatly oversimplified 

    def __init__(self,foo,bar): 
     self._foo = foo 
     self._bar = bar 
     pass 

    def __call__(self,decorated_function): 
     return self 

    def __get__(self,instance,type): 

     if not instance: 
      return self 

     return 10 

class ClassA: 

    @property 
    def foo(self): return "hello" 

    @MyCustomDescriptor(foo="a",bar="b") 
    def bar(self): pass 

    @MyCustomDescriptor(foo="c",bar="d") 
    def baz(self): pass 

class ClassB(ClassA): 

    @property 
    def something_we_dont_care_about(self): return "world" 

    @MyCustomDescriptor(foo="e",bar="f") 
    def blah(self): pass 

# This will get attributes on the specified type (class) that are of matching_attribute_type. It just returns the attributes themselves, not their names. 
def get_attributes_of_matching_type(type,matching_attribute_type): 

    return_value = [] 

    for member in inspect.getmembers(type): 

     member_name = member[0] 
     member_instance = member[1] 

     if isinstance(member_instance,matching_attribute_type): 
      return_value.append(member_instance) 

    return return_value 

# This will return a dictionary of name & instance of attributes on type that are of matching_attribute_type (useful when you're looking for attributes marked with a particular descriptor) 
def get_attribute_name_and_instance_of_matching_type(type,matching_attribute_type): 

    return_value = {} 

    for member in inspect.getmembers(ClassB): 

     member_name = member[0] 
     member_instance = member[1] 

     if isinstance(member_instance,matching_attribute_type): 
      return_value[member_name] = member_instance 

    return return_value 
+0

¿Qué harás exactamente con la lista de atributos una vez que la tengas? –

+0

Las clases en cuestión representan mensajes que se han deserializado. Los mensajes usan herencia cuando comparten campos. Hay un método que necesita para imprimir información sobre cada campo de cada mensaje. Cada atributo de estas clases de mensaje está realmente marcado con un descriptor, pero dejé ese detalle ya que pensé que era irrelevante. –

+0

... Entonces, ¿actualiza el descriptor para que agregue el atributo descrito a una lista? O, y esto podría ser un poco demasiado radical, ¿usar un dic con claves de cadena en lugar de atributos separados? –

Respuesta

9

usted debe utilizar el módulo del pitón inspect por tales capacidades introspectivas.

. 
. 
>>> class ClassC(ClassB): 
...  def baz(self): 
...   return "hiya" 
... 
>>> import inspect 
>>> for attr in inspect.getmembers(ClassC): 
... print attr 
... 
('__doc__', None) 
('__module__', '__main__') 
('bar', <property object at 0x10046bf70>) 
('baz', <unbound method ClassC.baz>) 
('foo', <property object at 0x10046bf18>) 

Leer más sobre el módulo hereinspect.

+0

Dulce, es como una mejor versión de dict porque mientras dict simplemente opera en la clase, inspecciona. Getmembers parece operar también sobre antepasados. Voy a publicar algunas muestras de código adicionales en mi enfoque original usando esta técnica. –

5

desea utilizar dir:

for attr in dir(ClassB): 
    print attr 
+0

Interesante. Supuse que dir() acaba de devolver las claves de __dict__ pero tienes razón, una diferencia clave es que dir() opera en la clase y todos sus antepasados, mientras que __dict__ parece operar solo en la clase (no en sus antepasados). Necesito acceso a los atributos en sí (no solo a los nombres) pero parece que puedo usar una combinación de dir() y getattr(). ¡Gracias! –

+1

Sip 'dir()' y 'gettattr()' es la forma normal de hacerlo. – zeekay

+0

Nota: El módulo 'inspeccionar' es sobre lo que creo que se basa el comando dir(). Creo que usa mro y getmembers() para encontrar "todos los atributos alcanzables" del objeto. – jdi

2

Lamentablemente no es un objeto compuesto individual. Cada acceso de atributo para un objeto python (normal) primero comprueba obj.__dict__, luego los atributos de todas sus clases base; si bien hay algunas cachés internas y optimizaciones, no hay un solo objeto al que pueda acceder.

Dicho esto, una cosa que podría mejorar su código es utilizar en lugar de cls.__mro__cls.__bases__ ... en lugar de los padres inmediatos de la clase, cls.__mro__ contiene todos los antepasados ​​de la clase, en el orden exacto de Python sería buscar, con toda antepasados ​​comunes que ocurren solo una vez. Eso también le permitiría a su método de búsqueda de tipo ser no recursivo. Sin apretar ...

def get_attrs(obj): 
    attrs = set(obj.__dict__) 
    for cls in obj.__class__.__mro__: 
     attrs.update(cls.__dict__) 
    return sorted(attrs) 

...hace una buena aproximación de la implementación predeterminada dir(obj).

+0

¡Oh, eso es genial! Parece que tengo dos opciones ahora. Zeekay sugirió usar dir() (que incluía los nombres de los atributos de las clases antecesoras) aunque necesitaría getattr() para cada nombre. O podría usar tu método. Mi ejemplo hizo que parezca que solo necesito los nombres de los atributos, pero en realidad sí necesito referencias a los atributos mismos. Entonces creo que usaré tu código. ¡Gracias! –

+0

Además, gracias por el consejo sobre __mro__. Creo que había visto a alguien usarlo, pero no me di cuenta de qué se trataba. ¡Eso es muy útil! –

+0

Así que esto devuelve solo nombres de atributos, ¿verdad? Estoy trabajando ahora en modificarlo para devolver los nombres de los atributos y sus referencias. Si ya tienes algo en mente para hacer eso, ¿podrías publicarlo? –

1

Aquí hay una función que escribí, en el día. La mejor respuesta es usar el módulo inspect, ya que el uso de __dict__ nos da TODAS las funciones (las nuestras + heredadas) y (¿TODAS?) Las propiedades Y los miembros de los datos. Donde inspect nos da suficiente información para descartar lo que no queremos.

def _inspect(a, skipFunctionsAlways=True, skipMagic = True): 
    """inspects object attributes, removing all the standard methods from 'object', 
    and (optionally) __magic__ cruft. 

    By default this routine skips __magic__ functions, but if you want these on 
    pass False in as the skipMagic parameter. 

    By default this routine skips functions, but if you want to see all the functions, 
    pass in False to the skipFunctionsAlways function. This works together with the 
    skipMagic parameter: if the latter is True, you won't see __magic__ methods. 
    If skipFunctionsAlways = False and skipMagic = False, you'll see all the __magic__ 
    methods declared for the object - including __magic__ functions declared by Object 

    NOT meant to be a comprehensive list of every object attribute - instead, a 
    list of every object attribute WE (not Python) defined. For a complete list 
    of everything call inspect.getmembers directly""" 

    objType = type(object) 
    def weWantIt(obj): 
     #return type(a) != objType 
     output= True 
     if (skipFunctionsAlways): 
      output = not (inspect.isbuiltin(obj)) #not a built in 

     asStr = "" 
     if isinstance(obj, types.MethodType): 
      if skipFunctionsAlways: #never mind, we don't want it, get out. 
       return False 
      else: 
       asStr = obj.__name__ 
       #get just the name of the function, we don't want the whole name, because we don't want to take something like: 
       #bound method LotsOfThings.bob of <__main__.LotsOfThings object at 0x103dc70> 
       #to be a special method because it's module name is special 
       #WD-rpw 02-23-2008 

       #TODO: it would be great to be able to separate out superclass methods 
       #maybe by getting the class out of the method then seeing if that attribute is in that class? 
     else: 
      asStr = str(obj) 

     if (skipMagic): 
      output = (asStr.find("__") == -1) #not a __something__ 

     return (output) 

    for value in inspect.getmembers(a, weWantIt): 
     yield value 
+0

¡Gracias por publicar eso! –

0
{k: getattr(ClassB, k) for k in dir(ClassB)} 

valores adecuados (en lugar de <property object...>) se presentarán cuando se utiliza ClassB ejemplo.

Y, por supuesto Puedes realizar un filtrado esto añadiendo cosas como if not k.startswith('__') al final.

Cuestiones relacionadas