Lo que estoy tratando de lograr es algo como esto:

class object:
    def __init__(self):
        WidthVariable(self)

        print self.width
        #Imagine I did this 60frames/1second later
        print self.width

#output:
>>0
>>25

Lo que quiero que suceda (como arriba): cuando se crea WidthVariable, una clase, agrega la variable width a la instancia del objeto . Esta variable actúa como una propiedad normal, pero en este caso particular es de solo lectura (solo se establece la variable fget). Además, fget llama a una función definida en WidthVariable que decide qué width devolver.

Sin embargo, no tengo idea de cómo hacer esto. Lo probé usando propiedades normales, pero descubrí que solo funcionan en clases y no por instancia; tenga en cuenta que el código que uso debe ser lo más similar posible al anterior (es decir, solo el código dentro del __init__ de {{X1 }} debe establecer la variable width, en ningún otro lugar). Además, self.width no puede funcionar, porque no sé cómo llamarlo como self.width(), quiero self.width (porque se mantiene con el resto del diseño que tengo).

Para aclarar, el código completo sería algo como esto:

class MyObject:
    def __init__(self)
        WidthVariable(self)
        print self.width

class WidthVariable:
    def __init__(self, object)
        object.width = property(self.get_width)

    def get_width(self):
        value = #do_stuff
        return value #The Value

#output:
>>25 #Whatever the Value was
1
QasimK 27 oct. 2009 a las 19:47

4 respuestas

La mejor respuesta

Como, como dice @Jonathan, los descriptores (incluidas las propiedades) son por clase, no por instancia, la única forma de obtener diferentes descriptores por instancia es hacer que cada instancia individualice su propia clase. Eso es bastante superficial y fácil en lo que respecta a la metaprogramación; -) ...:

class Individualistic(object_or_whatever_bases):
  def __init__(self, whatever_args):
    self.__class__ = type('GottaBeMe', (self.__class__, object), {})
    # keep rocking...!-)

También estoy agregando object explícitamente porque es necesario (en Python 2.*, ¡y dices que eso es lo que estás usando!) Para que las clases sean de nuevo tipo. Nunca más use clases heredadas, no se comportan correctamente con respecto a las propiedades y mucho más (y por compatibilidad con versiones anteriores que no pueden: en Python 3, las clases heredadas finalmente se han aniquilado, por lo que CADA clase es de nuevo estilo sin ¡el requisito de heredar explícitamente de un objeto más!).

Ahora, cualquier descriptor que se coloque en self.__class__.__dict__ solo afectará esta instancia, ninguna otra. Hay un poco de sobrecarga (cada clase GottaBeMe y, por lo tanto, cada instancia tiene su propia __dict__, etc.), pero nada demasiado terrible.

Ahora, todo lo que se necesita para satisfacer la solicitud original es cambiar:

class WidthVariable:
    def __init__(self, object)
        object.width = property(self.get_width)

(también cambiar el nombre del argumento object con sensatez para evitar pisotear un dispositivo incorporado y hacer que la clase tenga un estilo nuevo porque SIEMPRE debe hacer que CADA clase tenga un estilo nuevo ;-), para:

class WidthVariable(object):
    def __init__(self, obj)
        obj.__class__.width = property(self.get_width)

¡Nada demasiado negro-mágico, como puedes ver! -)

5
Alex Martelli 27 oct. 2009 a las 20:09

No entiendo la forma en que has construido tu ejemplo, ni entiendo lo que quieres decir con "propiedades normales" que solo funcionan "en clases". Así es como se crea una propiedad de solo lectura:

class Foo(object):
    # create read-only property "rop"
    rop = property(lambda self: self._x)

    def __init__(self):
        self._x = 0

    def tick(self):
        self._x += 1    

f = Foo()
print f.rop # prints 0
f.tick()
f.tick()
print f.rop # prints 2
f.rop = 4 # this will raise AtributeError
0
Jonathan Feinberg 27 oct. 2009 a las 17:19

¿No tienes muy claro lo que quieres? pero supongo que desea que obj.width se personalice para cada instancia. Aquí hay una manera fácil mediante el uso de propiedades simples, la propiedad del ancho devuelve el valor devuelto por una devolución de llamada por instancia

class MyClass(object):
    def __init__(self, callback):
        self.callback = callback

    def get_width(self):
        return self.callback()

    width = property(get_width)

def w1(): return 0
def w2(): return 25

o1 = MyClass(w1)
o2 = MyClass(w2)

print o1.width
print o2.width

Si no se puede pasar la devolución de llamada, podemos asignarla a WidthVariable que devuelve el ancho en función de la instancia

class MyClass(object):
    def __init__(self):
        self.callback = WidthVariable(self)

    def get_width(self):
        return self.callback()

    width = property(get_width)

class WidthVariable(object):
    def __init__(self, obj):
        self.obj = obj

    def __call__(self):
        return hash(self.obj)

o1 = MyClass()
o2 = MyClass()

print o1.width
print o2.width
0
Anurag Uniyal 28 oct. 2009 a las 05:04

Creo que ahora entiendo tu pregunta, y también creo que eres sin suerte.

Para las clases de estilo nuevo, las invocaciones implícitas de métodos especiales solo funcionan correctamente si se definen en el tipo de un objeto, no en el diccionario de instancias del objeto.

Los descriptores (que se usan para implementar propiedades) deben aparecer en la clase __dict__, y no pueden aparecer en la instancia __dict__. En otras palabras, ¡Python no es Ruby!

Espero felizmente la corrección de un metaprogramador de Python piadoso, pero creo que tengo razón.

0
Jonathan Feinberg 27 oct. 2009 a las 18:47