Usar propiedades definidas en un nivel por instancia, no por clase
-
06-07-2019 - |
Pregunta
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
al objeto instancia . 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, el 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 intenté 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 WidthVariable
debería 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
Solución
Dado que, 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 hacer clases 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 propio __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 renombra el argumento object
razonablemente para evitar pisotear un incorporado, y hacer que la clase sea de nuevo estilo porque SIEMPRE debe hacer que CADA clase sea de nuevo estilo ;-), para:
class WidthVariable(object):
def __init__(self, obj)
obj.__class__.width = property(self.get_width)
¡Nada demasiado negro-mágico, como puedes ver! -)
Otros consejos
No entiendo la forma en que ha construido su ejemplo, ni entiendo lo que quiere decir sobre "propiedades normales". solo trabajando "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
Creo que ahora entiendo tu pregunta, y también creo que eres sin suerte .
LosPara clases de estilo nuevo, implícito las invocaciones de métodos especiales son solo se garantiza que funcione correctamente si definido en el tipo de un objeto, no en el diccionario de instancias del objeto.
descriptores (que se utilizan 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.
¿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 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