2012-05-03 15 views
8

Por lo general, el descriptor de Python se define como atributos de clase. Pero en mi caso, quiero que cada instancia de objeto tenga diferentes descriptores de conjunto que dependen de la entrada. Por ejemplo:¿Crear descriptor de propiedad por instancia?

class MyClass(object): 
    def __init__(self, **kwargs): 
    for attr, val in kwargs.items(): 
     self.__dict__[attr] = MyDescriptor(val) 

Cada objeto es tener un conjunto diferente de atributos que se decide en tiempo de creación de instancias. Dado que estos son objetos únicos, no es conveniente primero subclasificarlos.

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 intento acceder al atributo, obtuve algo como

<property at 0x4067cf0> 

¿Sabes por qué esto no funciona? ¿Hay algún trabajo alrededor?

+0

¿Por qué no utilizar diferentes subclases de MyClass con descripciones anuladas? – KurzedMetal

+0

Editado para agregar más ejemplos. Supongo que el descriptor por instancia simplemente no se puede hacer. He trabajado alrededor utilizando __getattr__. Sin embargo, aún no entiendo la restricción del lenguaje subyacente. –

+0

Los descriptores solo funcionan en el nivel de clase, lo siento. –

Respuesta

2

Esto no funciona porque hay que asignar el descriptor de la clase del objeto.

class Descriptor: 

    def __get__(...): 
     # this is called when the value is got 

    def __set__(... 
    def __del__(... 

si se escribe

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 que no funciona debido a que el tipo dictionairy se busca para los descriptores y no dictionairy objeto.

hay arounds de trabajo posible:

  1. poner el descriptor en la clase y hacer que use, por ejemplo, obj.xxxattr para almacenar el valor. Si solo hay un comportamiento de descriptor, esto funciona.

  2. sobrescribir setattr y getattr y delattr para responder a discriptors.

  3. poner un discriptor en la clase que responde a los descriptores almacenados en el dictionairy objeto.

2

Está utilizando descriptores de forma incorrecta.

descriptores no tienen sentido en un nivel de instancia. Después de darle todos los métodos __get__/__set__ acceso a la instance de la clase.

Sin saber exactamente lo que quiere hacer, le sugiero que ponga la lógica por instancia dentro del método __set__, marcando quién es el "que llama/instancia" y actúe en consecuencia.

De lo contrario nos dice lo que está tratando de lograr, por lo que podemos proponer soluciones alternativas.

1

La razón por la que no está funcionando es porque Python sólo verifica si hay descriptores cuando se esté buscando atributos de la clase, no en la instancia; los métodos en cuestión son:

Es posible reemplazar esos métodos en su clase con el fin de poner en práctica el descriptor protocol en instancias, así como 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 utiliza 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 los siguientes:

hola!

GRANDE

really?


no puede eliminar el atributo

hola!


no puede eliminar el atributo

realmente?

1

Creo dinámicamente instancias por exec creando 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 
+0

Por el lado bueno, el rendimiento será mejor que con [mi respuesta] (http://stackoverflow.com/a/35657853/208880); 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. –

Cuestiones relacionadas