¿Crear descriptor de propiedad por instancia?
Frecuentes
Visto 892 veces
8
Por lo general, los descriptores de Python se definen como atributos de clase. Pero en mi caso, quiero que cada instancia de objeto tenga diferentes descriptores establecidos que dependen de la entrada. Por ejemplo:
class MyClass(object):
def __init__(self, **kwargs):
for attr, val in kwargs.items():
self.__dict__[attr] = MyDescriptor(val)
Cada objeto tiene un conjunto diferente de atributos que se deciden en el momento de la instanciación. Dado que estos son objetos únicos, no es conveniente subclasificarlos primero.
tv = MyClass(type="tv", size="30")
smartphone = MyClass(type="phone", os="android")
tv.size # do something smart with the descriptor
Asignar Descriptor al objeto no parece funcionar. Si trato de acceder al atributo, obtengo algo como
<property at 0x4067cf0>
¿Sabes por qué esto no funciona? ¿Hay algún trabajo alrededor?
5 Respuestas
2
Esto no funciona porque debe asignar el descriptor a la clase del objeto.
class Descriptor:
def __get__(...):
# this is called when the value is got
def __set__(...
def __del__(...
Si tú escribes
obj.attr
=> type(obj).__getattribute__(obj, 'attr') is called
=> obj.__dict__['attr'] is returned if there else:
=> type(obj).__dict__['attr'] is looked up
if this contains a descriptor object then this is used.
por lo tanto, no funciona porque se buscan descriptores en el diccionario de tipos y no en el diccionario de objetos.
hay posibles soluciones:
coloque el descriptor en la clase y haga que use, por ejemplo, obj.xxxattr para almacenar el valor. Si solo hay un comportamiento de descriptor, esto funciona.
exagerar setattr y obtener y delattr para responder a los descriptores.
coloque un descriptor en la clase que responda a los descriptores almacenados en el diccionario de objetos.
contestado el 03 de mayo de 12 a las 18:05
2
Está utilizando los descriptores de forma incorrecta.
Los descriptores no tienen sentido a nivel de instancia. Después de todo el __get__
/__set__
métodos le dan acceso a la instance
de la clase.
Sin saber qué es exactamente lo que quiere hacer, le sugiero que coloque la lógica por instancia dentro del __set__
método, comprobando quién es el "llamador/instancia" y actuar en consecuencia.
De lo contrario, díganos qué está tratando de lograr, para que podamos proponer soluciones alternativas.
contestado el 03 de mayo de 12 a las 19:05
1
Esto parece un caso de uso para tuplas con nombre
Respondido 28 Oct 13, 16:10
1
La razón por la que no funciona es porque Python solo busca descriptores cuando busca atributos en la clase, no en la instancia; los métodos en cuestión son:
Es posible anular esos métodos en su clase para implementar el protocolo descriptor tanto en instancias como en clases:
# do not use in production, example code only, needs more checks
class ClassAllowingInstanceDescriptors(object):
def __delattr__(self, name):
res = self.__dict__.get(name)
for method in ('__get__', '__set__', '__delete__'):
if hasattr(res, method):
# we have a descriptor, use it
res = res.__delete__(name)
break
else:
res = object.__delattr__(self, name)
return res
def __getattribute__(self, *args):
res = object.__getattribute__(self, *args)
for method in ('__get__', '__set__', '__delete__'):
if hasattr(res, method):
# we have a descriptor, call it
res = res.__get__(self, self.__class__)
return res
def __setattr__(self, name, val):
# check if object already exists
res = self.__dict__.get(name)
for method in ('__get__', '__set__', '__delete__'):
if hasattr(res, method):
# we have a descriptor, use it
res = res.__set__(self, val)
break
else:
res = object.__setattr__(self, name, val)
return res
@property
def world(self):
return 'hello!'
Cuando la clase anterior se usa de la siguiente manera:
huh = ClassAllowingInstanceDescriptors()
print(huh.world)
huh.uni = 'BIG'
print(huh.uni)
huh.huh = property(lambda *a: 'really?')
print(huh.huh)
print('*' * 50)
try:
del huh.world
except Exception, e:
print(e)
print(huh.world)
print('*' * 50)
try:
del huh.huh
except Exception, e:
print(e)
print(huh.huh)
Los resultados son:
¡Hola!
BIG
¿De Verdad?
no se puede eliminar el atributo
¡Hola!
no se puede eliminar el atributo
¿De Verdad?
Respondido el 20 de junio de 20 a las 10:06
1
Creo dinámicamente instancias por exec
una clase inventada. Esto puede adaptarse a su caso de uso.
def make_myclass(**kwargs):
class MyDescriptor(object):
def __init__(self, val):
self.val = val
def __get__(self, obj, cls):
return self.val
def __set__(self, obj, val):
self.val = val
cls = 'class MyClass(object):\n{}'.format('\n'.join(' {0} = MyDescriptor({0})'.format(k) for k in kwargs))
#check if names in kwargs collide with local names
for key in kwargs:
if key in locals():
raise Exception('name "{}" collides with local name'.format(key))
kwargs.update(locals())
exec(cls, kwargs, locals())
return MyClass()
Prueba
In [577]: tv = make_myclass(type="tv", size="30")
In [578]: tv.type
Out[578]: 'tv'
In [579]: tv.size
Out[579]: '30'
In [580]: tv.__dict__
Out[580]: {}
Pero las instancias son de diferente clase.
In [581]: phone = make_myclass(type='phone')
In [582]: phone.type
Out[582]: 'phone'
In [583]: tv.type
Out[583]: 'tv'
In [584]: isinstance(tv,type(phone))
Out[584]: False
In [585]: isinstance(phone,type(tv))
Out[585]: False
In [586]: type(tv)
Out[586]: MyClass
In [587]: type(phone)
Out[587]: MyClass
In [588]: type(phone) is type(tv)
Out[588]: False
respondido 04 mar '16, 11:03
En el lado bueno, el rendimiento será mejor que con mi respuesta; en el lado negativo, las instancias no son de la misma clase y podría haber confusión porque todas tienen el mismo nombre de clase. Aún así, una buena solución en las circunstancias apropiadas. - ethan furman
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas python metaprogramming propertydescriptor or haz tu propia pregunta.
¿Por qué no usar diferentes subclases de MyClass con descriptores anulados? - KurzedMetal
Editado para agregar más ejemplos. Supongo que el descriptor por instancia simplemente no se puede hacer. He trabajado alrededor de él usando obtener. Sin embargo, todavía no entiendo la restricción de idioma subyacente. - Wai Yip Tung
Los descriptores solo funcionan a nivel de clase, lo siento. - Martijn Pieters
Puedes explicar el porqué quieres hacer esto? parece un
NamedTuple
o con unadict
sería suficiente como una estructura de datos. - Jared Goguen