2011-01-05 19 views
5

Tenga una mirada en el siguiente código:¿Cómo acceder a los atributos de clase de una superclase en Python?

class A(object): 
    defaults = {'a': 1} 

    def __getattr__(self, name): 
     print('A.__getattr__') 
     return self.get_default(name) 

    @classmethod 
    def get_default(cls, name): 
     # some debug output 
     print('A.get_default({}) - {}'.format(name, cls)) 
     try: 
      print(super(cls, cls).defaults) # as expected 
     except AttributeError: #except for the base object class, of course 
      pass 

     # the actual function body 
     try: 
      return cls.defaults[name] 
     except KeyError: 
      return super(cls, cls).get_default(name) # infinite recursion 
      #return cls.__mro__[1].get_default(name) # this works, though 

class B(A): 
    defaults = {'b': 2} 

class C(B): 
    defaults = {'c': 3} 


c = C() 
print('c.a =', c.a) 

tengo una jerarquía de clases cada uno con su propio diccionario que contiene algunos valores por defecto. Si una instancia de una clase no tiene un atributo particular, se debe devolver un valor predeterminado para ella. Si no hay ningún valor predeterminado para el atributo en el diccionario de la clase actual defaults, se debe buscar el diccionario de la superclase defaults.

estoy tratando de poner en práctica esta clase utilizando el método recursivo get_default. El programa se queda atascado en una recursión infinita, desafortunadamente. Mi comprensión de super() es, obviamente, deficiente. Al acceder al __mro__, puedo hacer que funcione correctamente, pero no estoy seguro de que esta sea una solución adecuada.

Tengo la sensación de que la respuesta está en algún lugar en this article, pero no he podido encontrarla todavía. Tal vez necesito recurrir a usar una metaclase?

editar: En mi solicitud, __getattr__ primero comprueba self.base. Si no es None, el atributo debe buscarse desde allí. Solo en el otro caso, se debe devolver un valor predeterminado. Probablemente podría anular __getattribute__. ¿Sería esa la mejor solución?

edición 2: A continuación se muestra un ejemplo extendido de la funcionalidad que estoy buscando. Actualmente se implementa utilizando __mro__ (sugerencia anterior de unutbu, a diferencia de mi método recursivo original). A menos que alguien pueda sugerir una solución más elegante, me complace usar esta implementación. Espero que esto aclare las cosas.

class A(object): 
    defaults = {'a': 1} 

    def __init__(self, name, base=None): 
     self.name = name 
     self.base = base 

    def __repr__(self): 
     return self.name 

    def __getattr__(self, name): 
     print(" '{}' attribute not present in '{}'".format(name, self)) 
     if self.base is not None: 
      print(" getting '{}' from base ({})".format(name, self.base)) 
      return getattr(self.base, name) 
     else: 
      print(" base = None; returning default value") 
      return self.get_default(name) 

    def get_default(self, name): 
     for cls in self.__class__.__mro__: 
      try: 
       return cls.defaults[name] 
      except KeyError: 
       pass 
     raise KeyError 

class B(A): 
    defaults = {'b': 2} 

class C(B): 
    defaults = {'c': 3} 


c1 = C('c1') 
c1.b = 55 

print('c1.a = ...'); print(' ...', c1.a) # 1 
print(); print('c1.b = ...'); print(' ...', c1.b) # 55 
print(); print('c1.c = ...'); print(' ...', c1.c) # 3 

c2 = C('c2', base=c1) 
c2.c = 99 

print(); print('c2.a = ...'); print(' ...', c2.a) # 1 
print(); print('c2.b = ...'); print(' ...', c2.b) # 55 
print(); print('c2.c = ...'); print(' ...', c2.c) # 99 

La salida:

c1.a = ... 
'a' attribute not present in 'c1' 
    base = None; returning default value 
    ... 1 

c1.b = ... 
    ... 55 

c1.c = ... 
'c' attribute not present in 'c1' 
    base = None; returning default value 
    ... 3 

c2.a = ... 
'a' attribute not present in 'c2' 
    getting 'a' from base (c1) 
'a' attribute not present in 'c1' 
    base = None; returning default value 
    ... 1 

c2.b = ... 
'b' attribute not present in 'c2' 
    getting 'b' from base (c1) 
    ... 55 

c2.c = ... 
    ... 99 
+4

¿Qué hay de colocar los valores predeterminados directamente en el espacio de nombres de la clase en lugar de utilizar el diccionario 'defaults'? Esto haría innecesaria cualquier magia, simplemente funcionaría. –

+0

@Sven Sabía que debería haber mencionado esto :) En mi aplicación, el '__getattr__' está primero revisando otro atributo (base). Solo si el otro atributo es Ninguno, se debe devolver el valor predeterminado. De lo contrario, el atributo debe buscarse en la base. –

+0

Sí, así es como funcionan los atributos de instancia/clase en Python. :) Verifique la respuesta de Toni. Afaik su código hará exactamente lo mismo que el tuyo. –

Respuesta

0

La solución propuesta en la segunda edición de la pregunta sigue siendo la única que proporciona todo lo que requiere mi aplicación. Si bien el código de unutbu podría ser más simple de entender, la solución __mro__ proporciona algunas ventajas de OMI (ver comentarios).

0

Debe utilizar name mangling aquí.

Renombrar defaults a __defaults

Eso le da a cada clase un atributo inequívoca para que no se confundan entre sí

8
No

realmente una respuesta sino una observación:

Esto parece overengineered a mí, una trampa común al buscar excusas para usar la magia de pitón.

Si puede molestarse en definir un dict defaults para una clase, ¿por qué no solo definir los atributos en su lugar? El efecto es el mismo.

class A: 
    a = 1 

class B(A): 
    b = 2 

class C(B): 
    c = 3 


c = C() 
print('c.a =', c.a) 

EDIT:

En cuanto a responder a la pregunta, probablemente utilizar __getattribute__ en combinación con mi sugerencia de esta manera:

def __getattribute__(self, name): 
    try: 
     return object.__getattribute__(self.base, name) 
    except AttributeError: 
     return object.__getattribute__(self, name) 
+0

Vale la pena mencionar que también puede establecer el atributo en c. Para que en la práctica obtengas una configuración de instancia, con un valor predeterminado definido en la clase. –

+0

Esto no servirá para el caso de uso que he agregado a la pregunta. –

+0

@Brecht Machiels: Sí lo hará. Intentalo. –

1

¿Qué tal:

class A(object): 
    def __init__(self,base=None): 
     self.a=1 
     if base is not None: 
      self.set_base(base) 
     super(A,self).__init__() 
    def set_base(self,base): 
     for key in ('a b c'.split()): 
      setattr(self,key,getattr(base,key)) 
class B(A): 
    def __init__(self,base=None): 
     self.b=2 
     super(B,self).__init__(base)   
class C(B): 
    def __init__(self,base=None): 
     self.c=3 
     super(C,self).__init__(base) 

c1=C() 
c1.b=55 
print(c1.a) 
print(c1.b) 
print(c1.c) 
# 1 
# 55 
# 3 

c2=C(c1) 
c2.c=99 
print(c2.a) 
print(c2.b) 
print(c2.c) 
# 1 
# 55 
# 99 

c1.set_base(c2) 
print(c1.a) 
print(c1.b) 
print(c1.c) 
# 1 
# 55 
# 99 
+0

Si base es * not * 'None', necesita devolver el atributo correspondiente de la base. ¿Pero supongo que no podemos obtener el nombre del atributo en la función de envoltura? –

+0

@Brecht Machiels: De hecho, es posible pasar parámetros adicionales a la función 'check_base_first', a la que se puede acceder desde' wrapper'. – unutbu

+1

@Brecht Machiels: aunque podría haber una manera de usar propiedades para hacer lo que desea, puede que no sea la solución correcta. El problema al que se enfrenta puede ser un síntoma de una mala decisión de diseño anteriormente. Si revisas tu pregunta para mostrar exactamente cómo quieres que se comporten tus objetos (con 'base's) quizás se pueda sugerir un mejor diseño. – unutbu

2

Creo que el problema es resultado de misun que entiende el propósito de super().

http://docs.python.org/library/functions.html#super

Esencialmente, envolviendo el objeto (o clase) en super() hace Python omitir la clase más recientemente heredada-cuando se hace búsqueda de atributos. En su código, esto da como resultado que se omita la clase C al buscar get_default, pero esto realmente no hace nada, ya que C no define un get_default de todos modos. Naturalmente, esto da como resultado un ciclo infinito.

La solución es definir esta función en cada clase que deriva de A. Esto se puede hacer usando una metaclase:

class DefaultsClass(type): 
    def __init__(cls, name, bases, dct): 

     def get_default(self, name): 
      # some debug output 
      print('A.get_default(%s) - %s' % (name, cls)) 
      try: 
       print(cls.defaults) # as expected 
      except AttributeError: #except for the base object class, of course 
       pass 

      # the actual function body 
      try: 
       return cls.defaults[name] 
      except KeyError: 
       return super(cls, self).get_default(name) # cooperative superclass 

     cls.get_default = get_default 
     return super(DefaultsClass, cls).__init__(name, bases, dct) 

class A(object): 
    defaults = {'a': 1} 
    __metaclass__ = DefaultsClass 

    def __getattr__(self, name): 
     return self.get_default(name) 



class B(A): 
    defaults = {'b': 2} 

class C(B): 
    defaults = {'c': 3} 


c = C() 
print('c.a =', c.a) 
print('c.b =', c.b) 
print('c.c =', c.c) 

resultados:

A.get_default(c) - <class '__main__.C'> 
{'c': 3} 
('c.c =', 3) 
A.get_default(b) - <class '__main__.C'> 
{'c': 3} 
A.get_default(b) - <class '__main__.B'> 
{'b': 2} 
('c.b =', 2) 
A.get_default(a) - <class '__main__.C'> 
{'c': 3} 
A.get_default(a) - <class '__main__.B'> 
{'b': 2} 
A.get_default(a) - <class '__main__.A'> 
{'a': 1} 
('c.a =', 1) 

deben tener en cuenta que la mayoría de Python la gente considerará que esta es una solución muy extraña, y solo debe usar esto si necesita realmente, tal vez para admitir el código heredado.

1

Para ser más claro sobre su caso de "base" frente a "predeterminado".

>>> class A(object): 
...  a = 1 
... 
>>> class B(A): 
...  b = 2 
... 
>>> class C(B): 
...  c = 3 
... 
>>> a = A() 
>>> b = B() 
>>> c = C() 
>>> 
>>> b.b = 23 
>>> b.a 
1 
>>> b.b 
23 
>>> c.a 
1 
>>> c.b 
2 
>>> c.c 
3 
>>> c.c = 45 
>>> c.c 
45 

Esto cubre su caso de uso indicado. No necesitas ninguna magia en absoluto. Si su caso de uso es algo diferente, explique de qué se trata y le diremos cómo hacerlo sin magia. ;)

Cuestiones relacionadas