Using __setattr__ and descriptors for python class

I am writing a python class that uses __setattr__ and __getattr__ to provide user access to attributes.

However, some attributes cannot be handled in a general way, so I was hoping to use descriptors for them.

The problem is that for the descriptor, the __get__ descriptor will be invoked in favor of __getattr__ instances, but when assigned to the __setattr__ attribute, __setattr__ will be invoked in favor of __set__ descriptors.

Example:

 class MyDesc(object): def __init__(self): self.val = None def __get__(self, instance, owner): print "MyDesc.__get__" return self.val def __set__(self, instance, value): print "MyDesc.__set__" self.val = value class MyObj(object): foo = MyDesc() def __init__(self, bar): object.__setattr__(self, 'names', dict( bar=bar, )) object.__setattr__(self, 'new_names', dict()) def __setattr__(self, name, value): print "MyObj.__setattr__ for %s" % name self.new_names[name] = value def __getattr__(self, name): print "MyObj.__getattr__ for %s" % name if name in self.new_names: return self.new_names[name] if name in self.names: return self.names[name] raise AttributeError(name) if __name__ == "__main__": o = MyObj('bar-init') o.bar = 'baz' print o.bar o.foo = 'quux' print o.foo 

prints:

 MyObj.__setattr__ for bar MyObj.__getattr__ for bar baz MyObj.__setattr__ for foo MyDesc.__get__ None 

The __set__ descriptor __set__ never called.

Since the definition of __setattr__ does not just redefine the behavior for a limited set of names, there is no clear place that can be delayed until object.__setattr__

Is there a recommended way to assign attributes to use a descriptor, if one is available, and __setattr__ otherwise?

+4
source share
2 answers

I think I come up with this, having an automatic marking mechanism that are descriptors in each class and wrap __setattr__ so that it __setattr__ normal behavior of the object for these names.

This can be easily achieved with a metaclass (and a decorator for __setattr__

 def setattr_deco(setattr_func): def setattr_wrapper(self, attr, value): if attr in self._descriptors: return object.__setattr__(self, attr, value) return setattr_func(self, attr, value) return setattr_wrapper class MiscSetattr(type): def __new__(metacls, name, bases, dct): descriptors = set() for key, obj in dct.items(): if key == "__setattr__": dct[key] = setattr_deco(obj) elif hasattr(obj, "__get__"): descriptors.add(key) dct["_descriptors"] = descriptors return type.__new__(metacls, name, bases, dct) # and use MiscSetattr as metaclass for your classes 
+3
source

One possible way:

 def __setattr__(self, name, value): print "MyObj.__setattr__ for %s" % name for cls in self.__class__.__mro__ + (self, ): if name in cls.__dict__: return object.__setattr__(self, name, value) print 'New name', name, value self.new_names[name] = value 

It checks to see if a name is defined in the class, base classes, or instances, and then calls object.__setattr__ , which will execute the __set__ descriptor.

Another way:

 def __setattr__(self, name, value): print "MyObj.__setattr__ for %s" % name try: object.__getattribute__(self, name) except AttributeError: print 'New name', name, value self.new_names[name] = value else: object.__setattr__(self, name, value) 

But it will call the __get__ descriptor.

PS

I'm not sure if all __mro__ members need to be checked, since MyObj will contain the inherited class members in __dict__ .

Maybe for cls in (self.__class__, self):... would be enough.

+4
source

Source: https://habr.com/ru/post/1395021/


All Articles