2011-12-20 19 views
6

En mis esfuerzos como aprendiz de pitón me quedé atrapado recientemente en algún comportamiento extraño (desde mi punto de vista) si traté de trabajar con atributos de clase. No me quejo, pero agradeceré algunos comentarios útiles para arrojar algo de luz sobre este tema.python: ¿atributo de clase/herencia variable con polimorfismo?

Para reducir un asunto complejo en una pregunta más concisa i formularía así:

Cuál es la forma "Pythonic" para asegurarse de que un atributo de clase se comporta más como una variable estática en un árbol de herencia?

Me parece que un atributo de clase se comporta como un valor predeterminado de "copia en lectura" con características polimórficas. Mientras haga operaciones de "solo lectura", se mantendrá como "singleton", , pero tan pronto como acceda al atributo de clase con una asignación a través de la clase o instancia derivada, se transforma en una nueva referencia perdiendo la relación con la referencia base heredada.

(tiene seguro de potencial de algunas de las características interessting, pero hay que entenderlo a abrazarla, por lo que una idea es muy apreciada.)

class A(object): 
    classvar = 'A' 
    def setclassvar(self, value): 
     A.classvar = value     
    def __str__(self): 
     return "%s id(%s) " %(A.classvar, hex(id(A.classvar))[2:-1].upper()) 

class A1(A): 
    pass 

class B(object): 
    classvar = 'B' 
    def setclassvar(self, value): 
     self.__class__.classvar = value    
    def __str__(self): 
     cvar = self.__class__.classvar 
     return "%s id(%s) " %(cvar, hex(id(cvar))[2:-1].upper()) 

class B1(B): 
    def setclassvar(self, value): 
     self.__class__.classvar = value 

a, a1 = A(), A1() 
a1.setclassvar('a') 
print "new instance A: %s" %a 
print "new instance A1: %s" %a 

b, b1 = B(), B1() 
b1.setclassvar('bb') 
print "new instance B: %s" %b 
print "new instance B1: %s" %b1 

a1.setclassvar('aa') 
print "new value a1: %s" %a 
print "new value a: %s" %a 

a1.classvar = 'aaa' 
print "direct access a1: %s id(%s)" %(a1.classvar, hex(id(a1.classvar))[2:-1].upper()) 
print "method access a1: %s" %a1 
print "direct access a: %s" %a 

produce lo siguiente:

new instance A: a id(B73468A0) 
new instance A1: a id(B73468A0) 
new instance B: B id(B73551C0) 
new instance B1: bb id(AD1BFC) 
new value a1: aa id(AD1BE6) 
new value a: aa id(AD1BE6) 
direct access a1: aaa id(A3A494) 
method access a1: aa id(AD1BE6) 
direct access a: aa id(AD1BE6) 

Por lo tanto, el acceso directo (asignación) object.classvar o mediado a través de self.__class__.classvar no es el lo mismo que BASECLASS.classvar.

Este es un problema de alcance o algo totalmente diferente.

Esperamos sus respuestas y gracias de antemano. :-)


Edición: Hubo una respuesta para un tiempo muy corto lo que sugiere el uso de la clase-descriptores como: How to make a class property?.

desgracia que no parece funcionar:

class Hotel(Bar): 
    def __init__(self):   
     Hotel.bar += 1 

hotel = Hotel() 
assert hotel.bar == 51 
assert hotel.bar == foo.bar 

La segunda aserción falla! hotel.bar no hace referencia al mismo objeto como foo.bar y hotel.bar referencias a otra cosa que a Hotel.bar!


segunda edición: yo soy muy consciente de que singletons se consideran un "anti patrón" y no tienen la intención de utilizarlos (extensivly). Por lo tanto, no los mencioné en la pregunta-titel. Aun así hay muchas soluciones que discuten y brindan soluciones con y sobre singletons, mi pregunta permanece: ¿Por qué una variable de clase puede separar su referencia tan fácilmente? Rubí se comporta más como se siente natural para mí: http://snippets.dzone.com/posts/show/6649

+0

Busque Python Singleton. (a) esto ha sido respondido y (b) por lo general, es una muy mala idea tratar de hacer Singletons. http://stackoverflow.com/search?q=python+singleton –

+0

posible duplicado de [Creación de un singleton en python] (http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python) –

+0

No estoy interesado en una discusión "antipatrina", así que por favor tengan paciencia conmigo en este tema, pero evitaron el problema de la herencia. Todas las soluciones que investigué no abordaron la característica polimórfica de una asignación, que mata a la referencia original. Ruby por otro lado, hace lo que parece más "natural" para mí (pero obviamente no para los demás): http://snippets.dzone.com/posts/show/6649. Así que supongo que es un tema de alcance de nombre o similar, pero todavía me estoy preguntando. –

Respuesta

1

Si la implementación es difícil de explicar, es una mala idea.

En este caso, incluyo una implementación, en aras de la completitud, y este es el tipo de cosas complicadas que me gustan en Python.

Por lo tanto, el fragmento a continuación abusa de cierres para crear un decorador de clases que satisfaga las necesidades del O.P.: una variable de clase que permanece "unificada" para leer y escribir en clases derivadas.

Además, como recompensa, envuelvo el atributo en un descriptor que hace que el atributo también sea inamovible en instancias, por lo que cada vez que se escribe el atributo en una subclase o instancia de una subclase de la clase original, la clase atributo se actualiza correctamente.

Como dice el Zen of Python: "Si la implementación es difícil de explicar, es una mala idea" - No creo que pueda venir con algo más difícil - estamos hablando de metadatos generados dinámicamente en el ámbito clases aquí. Es funciona, pero pierde este código "unpythonic", ya que es muy críptico debido al uso intensivo de la clase, la metaclase, los cierres y los mecanismos de descripción.

def SingletonAttrs(**names): 
    keys = names.keys() 
    def class_decorator(cls): 
     class Meta(type): 
      def __getattribute__(cls, attr): 
       if attr in keys: 
        return type.__getattribute__(owner_cls, attr) 
       return type.__getattribute__(cls, attr) 
      def __setattr__(cls, attr, value): 
       if attr in keys: 
        class Wrapper(object): 
         def __init__(self, value): 
          self.__set__(None, value) 
         __set__ = lambda self, instance, value: setattr(owner_cls,"__" + attr, value) 
         __get__ = lambda self, instance, owner: type.__getattribute__(owner_cls, "__" + attr) 
        return type.__setattr__(owner_cls, attr, Wrapper(value)) 
       return type.__setattr__(cls, attr, value) 
     owner_cls = Meta(cls.__name__, cls.__bases__, cls.__dict__.copy()) 
     for key in keys: 
      setattr(owner_cls, key, names[key]) 
     return owner_cls 
    return class_decorator 

if __name__ == "__main__": 

    @SingletonAttrs(a="value 1", b="value 2") 
    class Test(object): 
     pass 

    class TestB(Test): 
     pass 

    t = Test() 
    print t.a 
    print t.b 
    tb = TestB() 
    tb.a = "value 3" 
    print Test.a 
+0

Tengo la sensación incesante de que se podría lograr con código de un orden de magnitud más simple. Pero solo me viene a la mente. – jsbueno

+0

Muchas gracias. Buen ejemplo Aprendí bastante, simplemente revisando tu implementación. Así que solo "en aras de la completitud", y para este es el tipo de cosas difíciles que me gustan ": ¿Estoy en lo cierto en mi suposición de que su solución es incluso un recolector de basura," seguro "? –

2
a1.classvar = 'aaa' 

Ésta es no una "referencia" a una variable de clase.

Esa es una nueva variable de instancia en el objeto 'a1'.

Una expresión como A.classvar es la variable de clase. El objeto de clase (y sus superclases) tienen un diccionario de nivel de clase (A.__dict__) con objetos de nivel de clase definidos en él. La resolución del nombre funciona verificando la clase, luego todas las superclases en Orden de resolución de método (MRO).

Una expresión como a.classvar se resuelve mediante una búsqueda a través del espacio de nombres del objeto. Cuando se trata de una referencia de "lectura", se busca el objeto y la clase (y las superclases).

Cuando aparece en el lado izquierdo de la asignación, la variable de instancia ("classvar") simplemente se crea en el objeto al que se hace referencia ("a"). No hay búsqueda en los espacios de nombres principales para resolver un nombre, ya que no hay nada que resolver. Está siendo creado.

+0

¡Genial! Eso es exactamente lo que sospechaba. ¿Entonces no hay una salida fácil? –

+0

Así que si quiero evitar el uso de BASECLASS.classvar todo el tiempo tendría a) para acceder al diccionario de clases de BASECLASS de alguna manera ob) obtener la referencia al objeto original antes de reasignarlo? Espera, supongo que b) no es posible porque crearía una nueva variable con un nuevo objeto asociado en un espacio de nombres higer si no usabas BASECLASS .__ dict__ ?! entonces tal vez c) definir una propiedad en BASECLASS encapsulando BASECLASS.classvar? –

+0

'clase A (objeto): _classvar = SOMEVALUE; ... classvar = property (getter, setter) ' –