Hijacking the getattr and setattr functions after __init__ completes - python

I need to intercept setattr and getattr after init completion, i.e. if main class doesn't have required attribute, it would look for it in subclass Extra, or when setting attribute, if it's not in main class then setting went to subclass Extra, how to understand that init was executed and intercept it only after completion? Here's the code I tried to do it with but it didn't work
class Test:
def __init__(self):
self.default_name = "Michael"
def __setattr__(self, key, value):
if not hasattr(self, key):
self.Extra.__dict__[key] = value;
self.__dict__[key] = v
def __getattr__(self, item):
if not hasattr(self, item):
return self.Extra.__dict__[item]
class Extra:
pass
user = Test()
user.default_name = "Tomas"
user.some_data = "test"
print(user.default_name)
print(user.some_data)

Direct operation attribute dictionary:
class Test:
def __init__(self):
vars(self)['default_name'] = "Michael"
vars(self)['extra'] = Test.Extra()
def __setattr__(self, key, value):
if key not in vars(self):
setattr(self.extra, key, value)
else:
vars(self)[key] = value
def __getattr__(self, item):
return getattr(self.extra, item)
class Extra:
pass
Test:
>>> user = Test()
>>> user.default_name
'Michael'
>>> user.default_name = 'Tomas'
>>> user.default_name
'Tomas'
>>> user.some_data = 'test'
>>> user.some_data
'test'
>>> vars(user)
{'default_name': 'Tomas', 'extra': <__main__.Test.Extra object at 0x000001D5151D6380>}

Related

Is there an equivalent of partialmethod for properties?

I have a class with many parameters that are stored in a dictionary as below (in practice, it's a bit more complex, but this gives a good idea). I am able to 'autogenerate' get and set methods using partialmethod:
for a_name in ['x', 'y', 'z']:
locals()["get_" + a_name] = partialmethod(_get_arg,
arg_name=a_name)
locals()["set_" + a_name] = partialmethod(_set_arg,
arg_name=a_name)
Ideally, I wanted to use #property, but only if I can 'autogenerate' #property, #setter and #deleter. Would it be possible? The second snippet show the 'manually' added properties; I am looking into using an equivalent of partialmethod to avoid repetition and unmaintainable code.
class C:
def __init__(self):
self.kwargs = {'x' : 0, 'y' : 3, 'z' : True}
def __get_arg(self, arg_name):
assert arg_name in self.kwargs
return self.kwargs[arg_name]
def __set_arg(self, arg_name, value):
assert arg_name in self.kwargs
self.kwargs[arg_name] = value
def __del_arg(self, arg_name):
assert arg_name in self.kwargs
del self.kwargs[arg_name]
#property
def x(self):
return self.__get_arg('x')
#x.setter
def x(self, value):
self.__set_arg('x', value)
#x.deleter
def x(self):
self.__del_arg('x')
#property
def y(self):
return self.__get_arg('y')
#y.setter
def y(self, value):
self.__set_arg('y', value)
#y.deleter
def y(self):
self.__del_arg('y')
#property
def z(self):
return self.__get_arg('z')
#x.setter
def z(self, value):
self.__set_arg('z', value)
#x.deleter
def z(self):
self.__del_arg('z')
c = C()
c.x = 'foo' # setter called
foo = c.x # getter called
del c.x # deleter called
After struggling for awhile, I came up with the following partialproperty descriptor.
The __get__ method is called when an attribute is accessed. In it I call the
setter() method with the first argument of obj for the self paramater
followed by the unpacked self.args and self.kwargs.
class partialproperty:
"""Combine the functionality of property() and partialmethod()"""
def __init__(self, getter, setter=None, deleter=None, *args, **kwargs):
self.getter = getter
self.setter = setter
self.deleter = deleter
self.args = args
self.kwargs = kwargs
def __set_name__(self, owner, name):
self._name = name
self._owner = owner
def __get__(self, obj, objtype=None):
return self.getter(obj, *self.args, **self.kwargs)
def __set__(self, obj, value):
if self.setter is None:
raise AttributeError(f"{self._owner.__class__.__name__} object can't set attribute: {self._name}")
self.setter(obj, *self.args, value, **self.kwargs)
def __delete__(self, obj):
if self.deleter is None:
raise AttributeError(f"{self._owner.__class__.__name__} object can't delete attribute: {self._name}")
self.deleter(obj, *self.args, **self.kwargs)
You can use it in a class to create property attributes like so, much like you
would with partialmethod().
If you don't need a setter and deleter, you need to either use keyword
arguments for your args, or explicitly pass None.
class RGBA:
def __init__(self, rgba):
self.rgba = rgba
def _component_(self, idx):
return self.rgba[idx]
# using keyword args
red = partialproperty(_component_, idx=0)
green = partialproperty(_component_, idx=1)
blue = partialproperty(_component_, idx=2)
# this has the same effect but with positional args
alpha = partialproperty(_component_, None, None, 3)
Then in your instances the respective attributes will behave like properties.
>>> color = RGBA([0, 75, 255, 1])
>>> color.red
0
>>> color.green
75
>>> color.blue
255
>>> color.alpha
1
For your example it would look like:
class C:
def __init__(self, **kwargs):
self.kwargs = kwargs
def __get_arg(self, arg_name):
try:
return self.kwargs[arg_name]
except KeyError:
raise AttributeError(arg_name)
def __set_arg(self, arg_name, value):
self.kwargs[arg_name] = value
def __del_arg(self, arg_name):
try:
del self.kwargs[arg_name]
except KeyError:
raise AttributeError(arg_name)
x = partialproperty(__get_arg, __set_arg, __del_arg, "x")
y = partialproperty(__get_arg, __set_arg, __del_arg, "y")
z = partialproperty(__get_arg, __set_arg, __del_arg, "z")
>>> obj = C(x=24)
>>> obj.x
24
>>> obj.y = 25
>>> obj.y
25
>>> del obj.y
>>> obj.y
AttributeError: y
>>> obj.kwargs["z"] = 26
>>> obj.z
26
You can write your own descriptor type, and use the __set_name__ method that will get called on it (by the class creation machinery) to figure out what name it's been saved to in the class:
class MyProp:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner=None):
if instance is None:
return self
return instance._get_arg(self.name)
def __set__(self, instance, value):
instance._set_arg(self.name, value)
def __delete__(self, instance):
instance._del_arg(self.name)
You'd use it this way:
class C:
# define __init__, _get_arg, etc.
x = MyProp()
y = MyProp()
z = MyProp()
Note that because it's code from another class calling the _X_arg methods, you probably don't want to do name mangling, so I've changed the __ prefixes to just a single _.
For your specific example, an easier way to do this would probably to subclass
dict then define the magic methods __getattr__, __setattr__, and
__delattr__ which are each called when an attribute doesn't exist. Then you
can call super() to inherit the behavior from the dict class.
class AttrDict(dict):
attrs = ("x", "y", "z")
def __getattr__(self, name):
if not name in self.attrs:
raise AttributeError(name)
try:
return super().__getitem__(name)
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
if not name in self.attrs:
raise AttributeError(name)
try:
return super().__setitem__(name, value)
except KeyError:
raise AttributeError(name)
def __delattr__(self, name):
if not name in self.attrs:
raise AttributeError(name)
try:
return super().__delitem__(name)
except KeyError:
raise AttributeError(name)
>>> obj = AttrDict(x=24)
>>> obj.x
24
>>> obj.a = 1
AttributeError: a
>>> obj.y = 25
>>> obj.y
25
>>> del obj.y
>>> obj.y
AttributeError: y
>>> obj.kwargs["z"] = 26
>>> obj.z
26
If you don't care about restricting to specific attributes it's even easier.
You can simply assign each __*attr__ method to its cooresponding
dict.__*item__ method, which is what gets called for subscription operations.
(ie obj[key])
class AttrDict(dict):
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
>>> obj = AttrDict({"a": 1})
>>> obj.a
1
>>> obj["b"] = 2
>>> obj.b
2
>>> del obj.b
>>> obj.b
KeyError

Changing attribute of class does not trigger setter and printing it does not trigger getter

I'm probably missing something really simple here.
I'm trying to use property() to add getter and setter functions to an attribute of MyClass.
class MyClass:
def __init__(self, attr):
self._attr = attr
def attr_setter(self, value):
print(f'Setting attr')
self._attr = value
def attr_getter(self):
print('Getting attr')
return self._attr
self.attr = property(fget=attr_getter, fset=attr_setter)
c = MyClass('something')
print(c.attr)
c.attr = 'something else'
print(c.attr)
However, the print statements and the assignment do not trigger attr_setter and attr_getter. I get the following output:
property object at <0x0000024A26B489A0>
something else
Find the details in comment
class MyClass:
def __init__(self, attr):
#self._attr = attr --> you might want to call the setter
self.attr_setter(attr)
# correcting the indentation
def attr_setter(self, value):
print(f'Setting attr')
self._attr = value
def attr_getter(self):
print('Getting attr')
return self._attr
attr = property(fget=attr_getter, fset=attr_setter)
c = MyClass('something') #Setting attr
print(c.attr) #Getting attr something
c.attr = 'something else' #Setting attr
print(c.attr) #Getting attr something else
# Setting attr
# Getting attr
# something
# Setting attr
# Getting attr
# something else

how to use instance attribute to define a class attribute?

I'm trying to use the name in my init for my class attribute, attr but it seems that's impossible.
here's the code:
class B:
def __init__(self, val):
self.val = val
def __get__(self, instance, owner):
return owner.valEditNew(self.val)
def __set__(self, instance, value):
return
class A:
def __init__(self, name = 'def_name'):
self.name = name
attr = B('G120')
def valEditNew(val):
val += ' #edited'
return val
a = A('JJ')
print(a.attr)
that's it if i use self.name or name or ... in place of G120>>>
builtins.NameError: name 'self' is not defined
if that's not possible, can you show me the way?
If you want to access attribute of the instance that contains the descriptor object, use instance parameter of __get__ / __set__:
class B:
def __get__(self, instance, owner):
return instance.valEditNew(instance.name) # <---
def __set__(self, instance, value):
return
class A:
attr = B()
def __init__(self, name='def_name'):
self.name = name
def valEditNew(self, val):
val += ' #edited'
return val
a = A('JJ')
print(a.attr)
# prints => JJ #edited

Implement _del_ method for a class with __getattribute__ overriden

Taking this question as a pointer, let's say there exists a class like the following:
class Container(object):
def __init__(self, **kwargs):
self._meta = defaultdict(lambda: None)
for attr, value in kwargs.iteritems():
self._meta[attr] = value
def __getattr__(self, key):
try:
return self._meta[key]
except KeyError:
raise AttributeError(key)
def __setattr__(self, key, value):
if key in ('_meta', '_hasattr'):
super(Container, self).__setattr__(key, value)
else:
self._meta[key] = value
This allows the following behavior:
c = Container()
c.a = 1
print(c.a) # 1
print(c.b) # None
Question: What is the best way to implement an operator such that the following works:
# Should delete the value of a from Container._meta
del c.a
Of course, one could obviously implement a method like,
def _delete(self, key):
...
But is there way to re-use a python operator to do this?
Just define the __delattr__ method:
def __delattr__(self, key):
del self._meta[key]

Catching changes to a mutable attribute in python

I am using properties to execute some code every time there is a change to an attribute, like this:
class SomeClass(object):
def __init__(self,attr):
self._attr = attr
#property
def attr(self):
return self._attr
#attr.setter
def attr(self,value):
if self._attr != value:
self._on_change()
self._attr = value
def _on_change(self):
print "Do some code here every time attr changes"
And this works great:
>>> a = SomeClass(5)
>>> a.attr = 10
Do some code here every time attr changes
But if I store a mutable object in attr instead, attr can be modified directly, bypassing the setter and my change-detection code:
class Container(object):
def __init__(self,data):
self.data = data
>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
>>>
Let's assume that attr is only ever going to be used to store an object of type Container. Is there an elegant way to modify SomeClass and/or Container to make SomeClass execute _on_change whenever the Container object stored in attr is modified? In other words, I want my output to be:
>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
Do some code here every time attr changes
Here is another solution. Some kind of proxy class. You dont need to modify any classes to monitor attributes changes in them, only wrap object in ChangeTrigger derived class with ovverriden _on_change function:
class ChangeTrigger(object):
def __getattr__(self, name):
obj = getattr(self.instance, name)
# KEY idea for catching contained class attributes changes:
# recursively create ChangeTrigger derived class and wrap
# object in it if getting attribute is class instance/object
if hasattr(obj, '__dict__'):
return self.__class__(obj)
else:
return obj
def __setattr__(self, name, value):
if getattr(self.instance, name) != value:
self._on_change(name, value)
setattr(self.instance, name, value)
def __init__(self, obj):
object.__setattr__(self, 'instance', obj)
def _on_change(self, name, value):
raise NotImplementedError('Subclasses must implement this method')
Example:
class MyTrigger(ChangeTrigger):
def _on_change(self, name, value):
print "New value for attr %s: %s" % (name, value)
class Container(object):
def __init__(self, data):
self.data = data
class SomeClass(object):
attr_class = 100
def __init__(self, attr):
self.attr = attr
self.attr_instance = 5
>>> a = SomeClass(5)
>>> a = MyTrigger(a)
>>>
>>> a.attr = 10
New value for attr attr: 10
>>>
>>> b = SomeClass(Container(5))
>>> b = MyTrigger(b)
>>>
>>> b.attr.data = 10
New value for attr data: 10
>>> b.attr_class = 100 # old value = new value
>>> b.attr_instance = 100
New value for attr attr_instance: 100
>>> b.attr.data = 10 # old value = new value
>>> b.attr.data = 100
New value for attr data: 100
Here is a version of SomeClass and Container that I think has the behavior you are looking for. The idea here being that modifications to Container will call the _on_change() function of the SomeClass instance that is associated with it:
class Container(object):
def __init__(self, data):
self.data = data
def __setattr__(self, name, value):
if not hasattr(self, name) or getattr(self, name) != value:
self.on_change()
super(Container, self).__setattr__(name, value)
def on_change(self):
pass
class SomeClass(object):
def __init__(self, attr):
self._attr = attr
self._attr.on_change = self._on_change
#property
def attr(self):
return self._attr
#attr.setter
def attr(self,value):
if self._attr != value:
self._on_change()
self._attr = value
def _on_change(self):
print "Do some code here every time attr changes"
Example:
>>> b = SomeClass(Container(5))
>>> b.attr.data = 10
Do some code here every time attr changes
>>> b.attr.data = 10 # on_change() not called if the value isn't changing
>>> b.attr.data2 = 'foo' # new properties being add result in an on_change() call
Do some code here every time attr changes
Note that the only change to SomeClass was the second line in __init__(), I just included the full code for completeness and easy testing.
If you want to track changes and don't want to mess with juggling with on_change() methods in different classes you could use functools.partial in the way shown starting here.
This way you can wrap your data and hide it totally. Get/change will be possible only via some methods melded inside that object.
NB: python has no private properties and on convention that we all are grownups and act against rules. In your case users of your api shouldn't change data on container (after creation) directly.
NB: here for those who may be interested in other ways...

Categories