Python property setter on a python list
Trying to summarize and exemplify my comments here. Hope this helps:
Solution #1: Use a regular attribute, and .append()
directly
The 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]
Solution #2: Use properties, and .append()
directly
The 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.
Solution #3: Subclass list
to allow the attribute to be callable
Which 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 :)