Why is the set-method bypassed when property is modified? - python

I'm not completely new into Python but sometimes I still have trouble with Python's logic or the way thinks are interpreted. I have the following Class called TestClass in the file testclass.py which has one property x and corresponding get and set methods.
class TestClass(object):
#property
def x(self):
print('get x')
return self.__x
#x.setter
def x(self, x):
print('set x')
self.__x = x
If I run a simple example, everything works as it should. So get and set methods are called and print their confirmation messages:
>>> from testclass import TestClass
>>> newObject = TestClass()
>>> newObject
<testclass.TestClass object at 0x0298B9D0>
>>> newObject.x = [1, 2, 3, 4]
set x
>>> newObject.x
get x
[1, 2, 3, 4]
My Problem is that if I just want to modify the property by indexing it, the get method is called (what I expect) to get the property from the object, but the set method is bypassed (no set message is printed, but the property is modified):
>>> newObject.x[1] = 99
get x
>>> newObject.x
get x
[1, 99, 3, 4]
For me, this behaviour is not very logical. I'm coming from Matlab (which is not the most elegant language for OOP). The same structure in Matlab would lead to the following procedure:
get method for x is been called to get x
replace the value at the specific index with the new value
call set method to overwrite the old version of x with the new x
This is just a small example. In my code, i need to get into the set method everytime when the property has been modified. Is there a pythonic way for this?
Thanks a lot!

The property.getter controls what the property returns when bound to an instance. Once that value is returned, the getter has no control on what you do with it. In particular, if the object was mutable, then you can update it without passing by the property.setter.
Returning a copy of the data
One simple way to fix that is to make your property return a view on the internal data, by example by returning a copy of the data instead of the data itself.
This will force the user to get the value, update it and then set it back.
import copy
class TestClass(object):
#property
def x(self):
print('get x')
return copy.deepcopy(self.__x)
#x.setter
def x(self, x):
print('set x')
self.__x = copy.deepcopy(x)
Example
newObject = TestClass()
newObject.x = [1, 2, 3, 4]
newObject.x[1] = 99
print(newObject.x)
Output
set x
get x
get x
[1, 2, 3, 4]
Using a controller class
Although, it can be costly to copy your data everytime, so an alternate solution is to wrap it in a controller class.
This can be done in a methodical way by inheriting from collections.MutableSequence which provides the usual methods available for a list based on your implementation of __delitem__, __setitem__, __getitem__, __len__ and insert.
The result will be an object that behaves exactly like a list, but with some hooks to check any mutation is valid.
from collections import MutableSequence
class ListController(MutableSequence):
def __init__(self, data):
self._data = data
def __setitem__(self, key, value):
print('index {} is being set to {}'.format(key, value))
self._data[key] = value
def __delitem__(self, key):
print('index {} is being deleted'.format(key))
del self._data[key]
def __getitem__(self, item):
return self._data[item]
def __len__(self):
return len(self._data)
def __repr__(self):
return repr(self._data)
def insert(self, index, value):
print('item {} is being inserted at index {}'.format(value, index))
self._data.insert(index, value)
class TestClass(object):
def __init__(self):
self.__x = [1, 2, 3, 4]
#property
def x(self):
print('get x')
return ListController(self.__x)
#x.setter
def x(self, x):
print('set x')
self.__x = list(x)
Example
newObject = TestClass()
newObject.x[1] = 99
print(newObject.x)
Output
get x
index 1 is being set to 99
get x
[1, 99, 3, 4]

Related

How to detect changes of a list whithin a Python class, or why does "setter" not trigger

I would like to have a list in a Python class. Whenever an element in the list is changed I need to run some logic. I'm pretty new to classes in python and my approach with the setter might be pretty naive. This is what makes intuitive sense to me:
class test():
def __init__(self):
self._R = [False]*16
#property
def R(self):
return self._R
#R.setter
def R(self,a):
print('Why do I not get reached?')
self._R = a
W = test()
W.R[0] = True
But the setter never gets triggered. If you could give me a notch in the right direction, I would be very great full.
You can create a new List-like class that takes a callback function and executes it whenever the list is changed:
class CallbackList: # PEP-8 style suggests UpperCase class names
def __init__(self, callback=None):
self._list = [False]
self._callback = callback # Python functions are first-class objects just like ints, strings, etc, so this is completely legal
def __setitem__(self, index, value):
self._list[index] = value
if self._callback:
self._callback() # Executes the callback function whenever a value is set
def __getitem__(self, index):
return self._list[index]
class Test:
def __init__(self):
self.callback_list = CallbackList(callback=self.foo)
def foo(self):
print("You changed the list!")
W = Test()
W.callback_list[0] = True # This prints "You changed the list!"
Note that this still won't catch every possible change. For example:
W = Test()
some_list = [1, 2, 3]
W.callback_list[0] = some_list # This triggers the callback function
print(W.callback_list[0]) # [1, 2, 3]
some_list.append(4) # This does NOT trigger the callback function!
print(W.callback_list[0]) # [1, 2, 3, 4] !
I tried to write code according to #user2357112supportsMonica comments:
class test():
def __init__(self):
self.A = 1
def foo(self):
print(self.A)
class R_Class():
def __init__(self):
self._R = [False]*16
def __setitem__(self,index,value):
self._R[index] = value
test.foo() #Here I need to call foo somehow
def __getitem__(self,index):
return self._R[index]
R = R_Class()
W = test()
W.R[0] = True
But this approach leads to another problem, is there a way to properly call the foo function from within the sub class?

Custom list class in Python 3 with __get__ and __set__ attributes

I would like to write a custom list class in Python 3 like in this question How would I create a custom list class in python?, but unlike that question I would like to implement __get__ and __set__ methods. Although my class is similar to the list, but there are some magic operations hidden behind these methods. And so I would like to work with this variable like with list, like in main of my program (see below). I would like to know, how to move __get__ and __set__ methods (fget and fset respectively) from Foo class to MyList class to have only one class.
My current solution (also, I added output for each operation for clarity):
class MyList:
def __init__(self, data=[]):
print('MyList.__init__')
self._mylist = data
def __getitem__(self, key):
print('MyList.__getitem__')
return self._mylist[key]
def __setitem__(self, key, item):
print('MyList.__setitem__')
self._mylist[key] = item
def __str__(self):
print('MyList.__str__')
return str(self._mylist)
class Foo:
def __init__(self, mylist=[]):
self._mylist = MyList(mylist)
def fget(self):
print('Foo.fget')
return self._mylist
def fset(self, data):
print('Foo.fset')
self._mylist = MyList(data)
mylist = property(fget, fset, None, 'MyList property')
if __name__ == '__main__':
foo = Foo([1, 2, 3])
# >>> MyList.__init__
print(foo.mylist)
# >>> Foo.fget
# >>> MyList.__str__
# >>> [1, 2, 3]
foo.mylist = [1, 2, 3, 4]
# >>> Foo.fset
# >>> MyList.__init__
print(foo.mylist)
# >>> Foo.fget
# >>> MyList.__str__
# >>> [1, 2, 3, 4]
foo.mylist[0] = 0
# >>> Foo.fget
# >>> MyList.__setitem__
print(foo.mylist[0])
# >>> Foo.fget
# >>> MyList.__getitem__
# >>> 0
Thank you in advance for any help.
How to move __get__ and __set__ methods (fget and fset respectively) from Foo class to MyList class to have only one class?
UPD:
Thanks a lot to #Blckknght! I tried to understand his answer and it works very well for me! It's exactly what I needed. As a result, I get the following code:
class MyList:
def __init__(self, value=None):
self.name = None
if value is None:
self.value = []
else:
self.value = value
def __set_name__(self, owner, name):
self.name = "_" + name
def __get__(self, instance, owner):
return getattr(instance, self.name)
def __set__(self, instance, value):
setattr(instance, self.name, MyList(value))
def __getitem__(self, key):
return self.value[key]
def __setitem__(self, key, value):
self.value[key] = value
def append(self, value):
self.value.append(value)
def __str__(self):
return str(self.value)
class Foo:
my_list = MyList()
def __init__(self):
self.my_list = [1, 2, 3]
print(type(self.my_list)) # <class '__main__.MyList'>
self.my_list = [4, 5, 6, 7, 8]
print(type(self.my_list)) # <class '__main__.MyList'>
self.my_list[0] = 10
print(type(self.my_list)) # <class '__main__.MyList'>
self.my_list.append(7)
print(type(self.my_list)) # <class '__main__.MyList'>
print(self.my_list) # [10, 5, 6, 7, 8, 7]
foo = Foo()
I don't know, that's Pythonic way or not, but it works as I expected.
In a comment, you explained what you actually want:
x = MyList([1])
x = [2]
# and have x be a MyList after that.
That is not possible. In Python, plain assignment to a bare name (e.g., x = ..., in contrast to x.blah = ... or x[0] = ...) is an operation on the name only, not the value, so there is no way for any object to hook into the name-binding process. An assignment like x = [2] works the same way no matter what the value of x is (and indeed works the same way regardless of whether x already has a value or whether this is the first value being assigned to x).
While you can make your MyList class follow the descriptor protocol (which is what the __get__ and __set__ methods are for), you probably don't want to. That's because, to be useful, a descriptor must be placed as an attribute of a class, not as an attribute of an instance. The properties in your Foo class creating separate instances of MyList for each instance. That wouldn't work if the list was defined on the Foo class directly.
That's not to say that custom descriptors can't be useful. The property you're using in your Foo class is a descriptor. If you wanted to, you could write your own MyListAttr descriptor that does the same thing.
class MyListAttr(object):
def __init__(self):
self.name = None
def __set_name__(self, owner, name): # this is used in Pyton 3.6+
self.name = "_" + name
def find_name(self, cls): # this is used on earlier versions that don't support set_name
for name in dir(cls):
if getattr(cls, name) is self:
self.name = "_" + name
return
raise TypeError()
def __get__(self, obj, owner):
if obj is None:
return self
if self.name is None:
self.find_name(owner)
return getattr(obj, self.name)
def __set__(self, obj, value):
if self.name is None:
self.find_name(type(obj))
setattr(obj, self.name, MyList(value))
class Foo(object):
mylist = MyListAttr() # create the descriptor as a class variable
def __init__(self, data=None):
if data is None:
data = []
self.mylist = data # this invokes the __set__ method of the descriptor!
The MyListAttr class is more complicated than it otherwise might be because I try to have the descriptor object find its own name. That's not easy to figure out in older versions of Python. Starting with Python 3.6, it's much easier (because the __set_name__ method will be called on the descriptor when it is assigned as a class variable). A lot of the code in the class could be removed if you only needed to support Python 3.6 and later (you wouldn't need find_name or any of the code that calls it in __get__ and __set__).
It might not seem worth writing a long descriptor class like MyListAttr to do what you were able to do with less code using a property. That's probably correct if you only have one place you want to use the descriptor. But if you may have many classes (or many attributes within a single class) where you want the same special behavior, you will benefit from packing the behavior into a descriptor rather than writing a lot of very similar property getter and setter methods.
You might not have noticed, but I also made a change to the Foo class that is not directly related to the descriptor use. The change is to the default value for data. Using a mutable object like a list as a default argument is usually a very bad idea, as that same object will be shared by all calls to the function without an argument (so all Foo instances not initialized with data would share the same list). It's better to use a sentinel value (like None) and replace the sentinel with what you really want (a new empty list in this case). You probably should fix this issue in your MyList.__init__ method too.

Injecting objects to class instance

I was wondering if the following is possible in python 2.7 or 3.x:
Class A:
def inject(self, **args):
... do something ...
def print(self):
print(self.x)
x = np.ones(10)
a = A()
a.inject(x)
a.print()
Note that I want inject to be very general and be able to add any object to the class instance.
What are your thoughts? Is this how I imagined possible?
Edit:
I also would like to inject many variables to the additional injected:
y = np.ones(10)
z = np.ones(10)
a.inject(y, z)
What you probably need is to use setattr(a, "x", x).
But if you want to use this call in inject(self, name, value) function, then it might be useful to add check which would prevent overwriting an existing attribute - You might want to use if hasattr(self, name): raise AttributeError('attribute already exists') or something like that. Without this check you might be quite surprised someday what is happenning with your objects after you have accidentally overwritten attributes. Just imagine 'accidental' a.inject("inject", x) ;)
But looking at your code, you are trying to use something like 'Python-with-classes' and it looks too 'java-ish'. In Python, you do not need to define inject() and print() in your class. You can simply write:
a = object()
x = 5
setattr(a, "x", x)
print(a.x) # btw. how does your implementation `a.print()` in the question knows that attribute `x` exists?
I you want to prevent overwriting existing attributes (i.e. allow only the first injections) and still be pythonic, define your class like this:
class A(object): # note the 'object' here, it is the 'new style' class
def __setattr__(self, name, value):
if hasattr(self, name):
raise AttributeError("attribute '{}' already exists".format(name))
object.__setattr__(self, name, value)
then you can write this:
a = A()
x = 5
a.x = x
a.x = 10 # raises error
If I understand your question correctly then you should use setattr:
class A:
def inject(self, name, value):
setattr(self, name, value)
def print(self):
print(self.x)
x = [1, 1]
a = A()
a.inject('x', x)
a.print()
>> [1, 1]

Create a list property in Python

I am starting OOP with Python 3 and I find the concept of property really interesting.
I need to encapsulate a private list, but how could I use this paradigm for lists?
Here's my naive try:
class Foo:
""" Naive try to create a list property.. and obvious fail """
def __init__(self, list):
self._list = list
def _get_list(self, i):
print("Accessed element {}".format(i))
return self._list[i]
def _set_list(self, i, new):
print("Set element {} to {}".format(i, new))
self._list[i] = new
list = property(_get_list, _set_list)
This doesn't behave as expected and even makes python crash when I try the following code. This is the fictive behavior I would like Foo to exhibit:
>>> f = Foo([1, 2, 3])
>>> f.list
[1, 2, 3]
>>> f.list[1]
Accessed element 1
2
>>> f.list[1] = 12
Set element 1 to 12
>>> f.list
[1, 12, 3]
import collections
class PrivateList(collections.MutableSequence):
def __init__(self, initial=None):
self._list = initial or []
def __repr__(self):
return repr(self._list)
def __getitem__(self, item):
print("Accessed element {}".format(item))
return self._list[item]
def __setitem__(self, key, value):
print("Set element {} to {}".format(key, value))
self._list[key] = value
def __delitem__(self, key):
print("Deleting element {}".format(key))
del self._list[key]
def __len__(self):
print("Getting length")
return len(self._list)
def insert(self, index, item):
print("Inserting item {} at {}".format(item, index))
self._list.insert(index, item)
class Foo(object):
def __init__(self, a_list):
self.list = PrivateList(a_list)
Then runnning this:
foo = Foo([1,2,3])
print(foo.list)
print(foo.list[1])
foo.list[1] = 12
print(foo.list)
Outputs:
[1, 2, 3]
Accessed element 1
2
Set element 1 to 12
[1, 12, 3]
There are some problems in your code. They might not be the only problems but fixing them would bring you further:
Properties are for new style classes. They are derived from object:
class Foo(object):
The getter (the first argument to property will be called without argument. So _get_list can't have the second argument i. The same applies to _set_list it can only have one argument, not two. (self is implicit and does not count here.)

Can you easily create a list-like object in python that uses something like a descriptor for its items?

I'm trying to write an interface that abstracts another interface somewhat.
The bottom interface is somewhat inconsistent about what it requires: sometimes id's, and sometimes names. I'm trying to hide details like these.
I want to create a list-like object that will allow you to add names to it, but internally store id's associated with those names.
Preferably, I'd like to use something like descriptors for class attributes, except that they work on list items instead. That is, a function (like __get__) is called for everything added to the list to convert it to the id's I want to store internally, and another function (like __set__) to return objects (that provide convenience methods) instead of the actual id's when trying to retrieve items from the list.
So that I can do something like this:
def get_thing_id_from_name(name):
# assume that this is more complicated
return other_api.get_id_from_name_or_whatever(name)
class Thing(object)
def __init__(self, thing_id):
self.id = thing_id
self.name = other_api.get_name_somehow(id)
def __eq__(self, other):
if isinstance(other, basestring):
return self.name == other
if isinstance(other, Thing):
return self.thing_id == other.thing_id
return NotImplemented
tl = ThingList()
tl.append('thing_one')
tl.append('thing_two')
tl[1] = 'thing_three'
print tl[0].id
print tl[0] == 'thing_one'
print tl[1] == Thing(3)
The documentation recommends defining 17 methods (not including a constructor) for an object that acts like a mutable sequence. I don't think subclassing list is going to help me out at all. It feels like I ought to be able to achieve this just defining a getter and setter somewhere.
UserList is apparently depreciated (although is in python3? I'm using 2.7 though).
Is there a way to achieve this, or something similar, without having to redefine so much functionality?
Yo don't need to override all the list methods -- __setitem__, __init__ and \append should be enough - you may want to have insert and some others as well. You could write __setitem__ and __getitem__ to call __set__ and __get__ methods on a sepecial "Thing" class exactly as descriptors do.
Here is a short example - maybe something like what you want:
class Thing(object):
def __init__(self, thing):
self.value = thing
self.name = str(thing)
id = property(lambda s: id(s))
#...
def __repr__(self):
return "I am a %s" %self.name
class ThingList(list):
def __init__(self, items):
for item in items:
self.append(item)
def append(self, value):
list.append(self, Thing(value))
def __setitem__(self, index, value):
list.__setitem__(self, index, Thing(value))
Example:
>>> a = ThingList(range(3))
>>> a.append("three")
>>> a
[I am a 0, I am a 1, I am a 2, I am a three]
>>> a[0].id
35242896
>>>
-- edit --
The O.P. commented: "I was really hoping that there would be a way to have all the functionality from list - addition, extending, slices etc. and only have to redefine the get/set item behaviour."
So mote it be - one really has to override all relevant methods in this way. But if what we want to avoid is just a lot of boiler plate code with a lot of functions doing almost the same, the new, overriden methods, can be generated dynamically - all we need is a decorator to change ordinary objects into Things for all operations that set values:
class Thing(object):
# Prevents duplicating the wrapping of objects:
def __new__(cls, thing):
if isinstance(thing, cls):
return thing
return object.__new__(cls, thing)
def __init__(self, thing):
self.value = thing
self.name = str(thing)
id = property(lambda s: id(s))
#...
def __repr__(self):
return "I am a %s" %self.name
def converter(func, cardinality=1):
def new_func(*args):
# Pick the last item in the argument list, which
# for all item setter methods on a list is the one
# which actually contains the values
if cardinality == 1:
args = args[:-1] + (Thing(args[-1] ),)
else:
args = args[:-1] + ([Thing(item) for item in args[-1]],)
return func(*args)
new_func.func_name = func.__name__
return new_func
my_list_dict = {}
for single_setter in ("__setitem__", "append", "insert"):
my_list_dict[single_setter] = converter(getattr(list, single_setter), cardinality=1)
for many_setter in ("__setslice__", "__add__", "__iadd__", "__init__", "extend"):
my_list_dict[many_setter] = converter(getattr(list, many_setter), cardinality="many")
MyList = type("MyList", (list,), my_list_dict)
And it works thus:
>>> a = MyList()
>>> a
[]
>>> a.append(5)
>>> a
[I am a 5]
>>> a + [2,3,4]
[I am a 5, I am a 2, I am a 3, I am a 4]
>>> a.extend(range(4))
>>> a
[I am a 5, I am a 0, I am a 1, I am a 2, I am a 3]
>>> a[1:2] = range(10,12)
>>> a
[I am a 5, I am a 10, I am a 11, I am a 1, I am a 2, I am a 3]
>>>

Categories

Resources