¿Crear descriptor de propiedad por instancia?

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?

preguntado el 03 de mayo de 12 a las 18:05

¿Por qué no usar diferentes subclases de MyClass con descriptores anulados? -

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. -

Los descriptores solo funcionan a nivel de clase, lo siento. -

Puedes explicar el porqué quieres hacer esto? parece un NamedTuple o con una dict sería suficiente como una estructura de datos. -

5 Respuestas

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:

  1. 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.

  2. exagerar setattr y obtener y delattr para responder a los descriptores.

  3. 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

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

Esto parece un caso de uso para tuplas con nombre

Respondido 28 Oct 13, 16:10

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

Creo dinámicamente instancias por execuna 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 or haz tu propia pregunta.