Create instance property descriptor?

Typically, a Python handle is defined as class attributes. But in my case, I want each instance of the object to have different set descriptors that depend on the input. For example:

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

Each object has a different set of attributes, which are defined during instance creation. Since these are disposable objects, do not first subclass them.

 tv = MyClass(type="tv", size="30") smartphone = MyClass(type="phone", os="android") tv.size # do something smart with the descriptor 

Assigning a handle to an object does not seem to work. If I try to access the attribute, I got something like

 <property at 0x4067cf0> 

Do you know why this does not work? Is there any work around?

+8
python metaprogramming
source share
5 answers

This does not work because you must assign a handle to the class of the object.

 class Descriptor: def __get__(...): # this is called when the value is got def __set__(... def __del__(... 

if you write

 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. 

so this will not work because the dictionairy type was looking for descriptors, not the dictionairy object.

there is a possible work around:

  • put the descriptor in the class and use it, for example. obj.xxxattr to save the value. If there is only one descriptor behavior, this works.

  • overwrite setattr and getattr and delattr to respond to descriptors.

  • puts the descriptor in a class that responds to descriptors stored in the dictionairy object.

+2
source share

You are using descriptors incorrectly.

Descriptors do not make sense at the instance level. In the end, the __get__ / __set__ methods give you access to the instance class.

Not knowing exactly what you want to do, I suggest you place an instance of logic inside the __set__ method, by checking who is the "caller / instance" and acts accordingly.

Otherwise, let us know what you are trying to achieve so that we can offer alternative solutions.

+2
source share

This looks like a precedent for named tuples.

+1
source share

The reason it doesn't work is because Python only checks for descriptors when looking for class attributes, not instance; methods considered:

You can override these methods in your class to implement the descriptor protocol for instances as well as classes:

 # 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!' 

When the above class is used as shown below:

 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) 

Results:

Hello!

Big

in fact?


cannot delete attribute

Hello!


cannot delete attribute

in fact?

+1
source share

I am dynamically creating exec instances for the created class. This may suit your use case.

 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() 

Test;

 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]: {} 

But instances have a different class.

 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 
+1
source share

All Articles