Catching changes to a mutable attribute in python - 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...

Related

How to create class from which I can use "get"?

In Python, I am trying to create a class that has attributes which I can "get" (sorry if this wording is not exactly correct).
Basically I am trying to define some class p which has attributes var1 and var2. So then I can use p.get("var1") and p.get("var2") to get the values of these respective attributes. How can I define something like this?
You can define a class with get() method and check if the instance has the attribute with built-in getattr() method as following:
class MyClass:
def get(self, property, default=None):
return getattr(self, property, default)
var1 = 'var1'
var2 = 'var2'
myInstance = MyClass()
print(myInstance.get('var1'))
print(myInstance.get('var3', 'NonExisting Attribute'))
Here's a working repl.it project that I just created: https://repl.it/#HarunYlmaz/OvalLiveMethod
You can also check if the instance has the attribute with hasattr() method:
class MyClass:
def get(self, property, default=None):
if hasattr(self, property):
return getattr(self, property)
else:
return default
# Or you can raise an exception here
For instance object
class Test:
def __init__(self):
self.a = 1
self.b = 2
def get(self, var):
return eval('self.%s' % var)
t = Test()
a = t.get('a')
print(a) ## output: 1
For class object
class Test:
a = 1
b = 2
#classmethod
def get(cls, var):
return eval('cls.%s' % var)
a = Test.get('a')
print(a) # output: 1

Clean way to implement setter and getter for lots of properties?

I have known the use of setter and getter for several properties, how could I trigger a same function when any property changes?
For example, the following codes add a setter to property a.
class AAA(object):
def __init__(self):
...
#property
def a(self):
...
#a.setter
def a(self, value):
...
If the class has a lot of properties like a, b, ... , z, and I want to print something like property xxx is modified when any property changes.
It is stupid to add the similar getter and setter one by one.
I have read some related questions and answers, but I do not find the solution for many properties.
How to trigger function on value change?
Using #property versus getters and setters
Metaprogramming, using __setattr__ to intercept modification:
class AAA(object):
def __setattr__(self, attr, value):
print("set %s to %s" % (attr, value))
super().__setattr__(attr, value)
aaa = AAA()
aaa.x = 17
# => set x to 17
print(aaa.x)
# => 17
You can do similarly with __getattr__ for reading access.
You can use descriptors. Descriptors are, in layman's terms, reusable properties. The advantage over the __getattr__ and __setattr__ hooks is that you have more fine-grained control over what attributes are managed by descriptors.
class MyDescriptor:
def __init__(self, default='default'):
self.default = default
def __set_name__(self, owner, name): # new in Python3.6
self.name = name
def __get__(self, instance, owner):
print('getting {} on {}'.format(self.name, instance))
# your getter logic here
# dummy implementation:
if instance is not None:
try:
return vars(instance)[self.name]
except KeyError:
return self.default
return self
def __set__(self, instance, value):
print('setting {} on {}'.format(self.name, instance))
# your getter logic here
# dummy implementation:
vars(instance)[self.name] = value
class MyClass:
a = MyDescriptor()
b = MyDescriptor()
_id = 1
# some logic for demo __repr__
def __init__(self):
self.c = 'non-descriptor-handled'
self.id = MyClass._id
MyClass._id += 1
def __repr__(self):
return 'MyClass #{}'.format(self.id)
Demo:
>>> m1 = MyClass()
>>> m2 = MyClass()
>>> m1.c
'non-descriptor-handled'
>>> m1.a
getting a on MyClass #1
'default'
>>> m1.b
getting b on MyClass #1
'default'
>>> m1.b = 15
setting b on MyClass #1
>>> m1.b
getting b on MyClass #1
15
>>> m2.b
getting b on MyClass #2
'default'
One year after asking this question, I find a more elgant way to add getter and setter to multiple similar properties.
Just make a more 'abstract' function which returns decorated property. And pass each of these properties to this function with a for loop. Then the getter and setter of all these properties are added.
def propABC(arg):
# arg: 'a', 'b', 'c'
#property
def prop(self):
_arg = '_' + arg
return getattr(self, _arg)
#prop.setter
def prop(self, val):
_arg = '_' + arg
setattr(self, _arg, val)
print(f"Set prop {_arg}")
return prop
for key in ['a', 'b', 'c']:
exec(f"{key} = propABC('{key}')")

Can decorator wrap setter and getter in python?

I want to build various setter and getter. Fot not copy and paste the code, I thought something to solve it. Can decorator do it?
#property
def !!variable_name!!(self):
return self.__!!variable_name!!
#!!variable_name!!.setter
def !!variable_name!!(self, input):
self.__!!variable_name!! = input
Is it possible like macro in C?
It's unclear why you would want to do something like this—create a property with setter that ignores its value argument—but the answer is "Yes", you can do it by creating a function that returns a custom property object:
However you can't use # syntax to apply it. Instead you have to utilize it as shown:
def attribute_property(name, input_value):
STORAGE_NAME = '_' + name
#property
def prop(self):
return getattr(self, STORAGE_NAME)
#prop.setter
def prop(self, ignored):
setattr(self, STORAGE_NAME, input_value)
return prop
# EXAMPLE USAGE
class Person(object):
name = attribute_property('name', 'Monty')
def __init__(self, name, age):
self.name = name # ignores value of passed "name" argument!
self.age = age
user = Person('Rodrigo', 42)
print('user.name: {!r}'.format(user.name))
print('user.age: {!r}'.format(user.age))
Output:
user.name: 'Monty'
user.age: 42
Simple answer: Yes, that's possible using the descriptor protocol. For example you want to save variables with a leading underscore and access them without the leading underscore such a descriptor would work:
from six import string_types
class DescriptorSingleLeadingUnderscore(object):
def __init__(self, attr, doc=""):
if not isinstance(attr, string_types):
# Not a string so take the documentation (if avaiable) and name
# from the method.
if attr.__doc__:
doc = attr.__doc__
attr = attr.__name__
self.__doc__ = doc # Set the documentation of the instance.
self.attr = '_' + attr # Add leading underscore to the attribute name
def __get__(self, instance, owner=None):
if instance is None:
return self
return getattr(instance, self.attr, None)
def __set__(self, instance, value):
setattr(instance, self.attr, value)
def __delete__(self, instance):
delattr(instance, self.attr)
class X(object):
someproperty = DescriptorSingleLeadingUnderscore('someproperty')
someproperty1 = DescriptorSingleLeadingUnderscore('someproperty1')
someproperty2 = DescriptorSingleLeadingUnderscore('someproperty2')
someproperty3 = DescriptorSingleLeadingUnderscore('someproperty3')
#DescriptorSingleLeadingUnderscore
def it_also_works_as_decorator(self):
pass # this code is never executed!
And a test case:
>>> x = X()
>>> x.someproperty = 100
>>> x.someproperty
100
>>> x._someproperty
100
>>> x.it_also_works_as_decorator = 100
>>> x.it_also_works_as_decorator
100
>>> x._it_also_works_as_decorator
100

sum two different class attributes

Lets say I have 2 class and I want to add the second classes attributes to first class I can make like that:
class first:
def __init__(self):
self.value_one = 2
self.value_two = 5
self.value_third = 7 #second class don't have that attribute
def sum_class(self, cls):
for attribute in cls.__dict__:
x = getattr(cls, attribute)
y = getattr(self, attribute)
setattr(self, attribute, x+y)
class second:
def __init__(self):
self.value_one = 3
self.value_two = 1
But it doesn't look pythonic is there any better way to do it?
My Classes will have more than 10 attributes so I don't want to add one by one that could be easy but massy code like:
def sum(self, cls):
self.value_one += cls.value_one
self.value_two += cls.value_two
Also my third class may have:
class ClassB:
def __init__(self):
self.value_one = 2
self.value_third = 3
I also want to able to add this class into my first class
The only class that have a behvaiour similar to what you are looking for is the Counter class:
>>> c = Counter()
>>> c['a'] = 1.0
>>> c + Counter('a')
Counter({'a': 2.0})
So you could store these "attributes" inside a Counter and use __getattr__ to use normal attribute access:
from collections import Counter
class ClassWithCounter:
def __init__(self, **kwargs):
self.counter = Counter(kwargs)
def __getattr__(self, attr):
# this allows to use the syntax: first().value_one
try:
return self.counter[attr]
except KeyError:
raise AttributeError(attr)
class first(ClasswithCounter):
def __init__(self):
super(first, self).__init__(value_one=2, value_two=5, value_third=7)
def sum_class(self, cls):
self.counter += cls.counter
class second(ClassWithCounter):
def __init__(self):
super(second, self).__init__(value_one=3, value_two=1)
Note however that the purpose of Counter is just to count things, so there may be some situations where it gives you strange results.
If that is the case you can simply implement your own dictionary-like class and use it in place of Counter.
Also a suggestion: given that you are writing this for a game, you should consider whether this kind of update is good or not. Because in this way the original "base values" for the player gets lost.
I personally would keep the "base values" separate and keep track of the "modifiers" to such values (e.g. bonuses or maluses provided by items, or temporary effects).
This apporach allows you to implement things like "the damage of this spell isn't affected by bonus armor" (so you just use the base value when computing the damage). Your current approach makes this more cumbersome.
you can make it shorter by using:
def sum_class(self, cls):
[setattr(self, attr, getattr(cls, attr) + getattr(self, attr)) for attr in cls.__dict__]
Edit 1:
It was unclear what you wanted, but after you sad in comments you want something like classA.__dict__ + classB.__dict_, maybe you can use this:
class sum_class:
def __init__(self, class_1, class_2):
self.sum = class_1.__class__()
self.class_2 = class_2
for attr in self.class_2.__dict__:
if attr in self.sum.__dict__:
setattr(self.sum, attr, getattr(self.class_2, attr) + getattr(self.sum, attr))
else:
setattr(self.sum, attr, getattr(self.class_2, attr))
class first:
def __init__(self):
self.value_one = 2
self.value_two = 5
self.value_third = 7 #second class don't have that attribute
def __add__(self, cls):
return sum_class(self, cls).sum
class second:
def __init__(self):
self.value_one = 3
self.value_two = 1
def __add__(self, cls):
return sum_class(self, cls).sum
when classes are defined like that then you can use it like this:
>>> f = first()
>>> s = second()
>>> x = f + s
>>> x.value_one
5
>>> x.value_two
6
>>> x.value_third
7

Automatically apply getter and setter to new object variable

Ok, lets say I have a really simple class i.e.:
class Test(object):
pass
What I would like to do is to define some default setter and getter methods
which are automatically applied to a new object member at creation time. In the example below a.x should always be uppercase, i.e.:
a = Test()
a.x = "foo"
print a.x
>>> FOO
If I create x within the class I would get this behavior like this:
class Test(object):
def __init__(self):
self._x = ""
#property
def x(self):
return self._x
#x.setter(self, string):
self._x = string.upper()
So is there any possibility to do this without defining setter and getter methods for each member ?? Thank a lot.
EDIT: With creation time I meant the creation time of a.x not of the class instance.
The simplest way is probably to override __setattr__, and change any string values to uppercase:
>>> class Test(object):
def __setattr__(self, attr, val):
if isinstance(val, basestring):
val = val.upper()
super(Test, self).__setattr__(attr, val)
>>> t = Test()
>>> t.x = 'foo'
>>> t.x
'FOO'
Subclass a dict;
In [1]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:class Struct(dict):
: """A dict subclass where you can simply use a dot to access attributes."""
:
: def __getattr__(self, name):
: return self[name]
:
: def __setattr__(self, name, value):
: self[name] = value
:--
In [2]: a = Struct()
In [3]: a.x = "foo"
In [4]: a.x
Out[4]: 'foo'
In [5]: a.length = 14
In [6]: a
Out[6]: {'length': 14, 'x': 'foo'}
That sounds like a use-case for pythons Descriptor Proctocol.
class WithDescriptors:
x = UpperCaseDescriptor()
y = UpperCaseDescriptor()
z = UpperCaseDescriptor()
class UperCaseDescriptor(object):
def __init__(self):
self.val = ''
def __get__(self, obj, objtype):
return self.val.upper()
def __set__(self, obj, val):
self.val = val
Thats just an outline and i didnt test the code to work!
If you want to extend such behaviour to every attribute of an instance,
even which are not existent, you should consider metaclasses.

Categories