I am trying to leverage on Python @property to modify a class attribute , which is of type List
. Most examples online assumes the attribute decorated by @property is a singular value, not a list that can be extended by setter.
To clarify question a bit : I don't just want to assign a value (a value of list of int) to property s, rather, I need to modify it (to append a new int to current list) .
My purpose is it is expected to have:
c = C()
c.s # [1,2,3] is the default value when instance c initiated.
c.s(5)
c.s # [1,2,3,5]
given implementation of C
as below:
class C:
def __init__(self):
self._s = [1,2,3]
@property
def s(self):
return self._s
@s.setter
def s(self, val):
self._s.append(val)
Now if I do c.s(5)
, I will get
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-99-767d6971e8f3> in <module>()
----> 1 c.s(5)
TypeError: 'list' object is not callable
I have read the most relevant posts : Python property on a list and Python decorating property setter with list
but neither is appropriate to my case:
__setitem__
can modify element of the list but i want to extend the list.
Use a global attribute is not acceptable for my current task.
In this regard, what is the best solution? (Or I should not expect @property on a mutable data structure from the beginning ? ) Thanks!
------edited-----
@Samuel Dion-Girardeau suggested to
subclass list and define its call magic method
, but I am not sure I understand the solution. Should I do something like this :
class C(list):
# somewhere when implementing class C
def __call__(self):
# even not sure what i need to do here
The @property is a built-in decorator for the property() function in Python. It is used to give "special" functionality to certain methods to make them act as getters, setters, or deleters when we define properties in a class.
@property is used to get the value of a private attribute without using any getter methods. We have to put a line @property in front of the method where we return the private variable. To set the value of the private variable, we use @method_name. setter in front of the method. We have to use it as a setter.
Trying to summarize and exemplify my comments here. Hope this helps:
.append()
directlyThe purpose of a setter is not to extend the value of the attribute, it is to replace it. In this regard, a list isn't any more different than an int or a string. In your case, since the value is a mutable list, you can simply call the .append()
method directly on it.
class C():
def __init__(self):
self.s = [1, 2, 3]
>>> c = C()
>>> c.s
[1, 2, 3]
>>> c.s.append(1)
>>> c.s
[1, 2, 3, 1]
>>> c.s = [0, 0]
>>> c.s
[0, 0]
.append()
directlyThe solution above works if there's nothing to check when getting/setting s
. However if you need to use a property for some reason (e.g. you have some computation or checks to do, and want to prevent users from setting anything they want as s
), you can do so with properties.
In this instance, I'm preventing negative numbers in the list as an example of a validation.
class C():
def __init__(self):
self._s = [1, 2, 3]
@property
def s(self):
return self._s
@s.setter
def s(self, val):
if any(x < 0 for x in val):
raise ValueError('No negative numbers here!')
self._s = val
>>> c = C()
>>> c.s
[1, 2, 3]
>>> c.s.append(1)
>>> c.s
[1, 2, 3, 1]
>>> c.s = [0, 0]
>>> c.s
[0, 0]
>>> c.s = [0, -1]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 10, in s
ValueError: No negative numbers here!
Note where the error is thrown here: not if I call c.s(...)
, which your question assumed to be calling the setter, but rather when I assign to c.s
, with c.s = ...
.
Also note that the users of this class will modify _s
indirectly, through the setter.
list
to allow the attribute to be callableWhich I absolutely don't recommend at all because it breaks every expectation the users of this class would have, and I'm only providing it as a trivia, and because it allows the behaviour you asked for initially.
class CallableList(list):
# This is the method that actually gets called when you `c.s(...)`!
def __call__(self, *args):
self.append(*args)
class C():
def __init__(self):
self._s = CallableList([1,2,3])
@property
def s(self):
return self._s
@s.setter
def s(self, val):
self._s = CallableList(val)
>>> c = C()
>>> c.s
[1, 2, 3]
>>> c.s(1)
>>> c.s
[1, 2, 3, 1]
>>> c.s = [0, 0]
>>> c.s
[0, 0]
>>> c.s(1337)
>>> c.s
[0, 0, 1337]
Please don't do this, and if you do make sure it's not traceable back to me :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With