I have a class that contains an object (set in this case). The set is modified by the standard function operators __add__
and __sub__
, but I also want to explicitly expose a method of getting this set.
Should the @property
decorator be used instead of a simple get()
method, even if there is no intent to allow setting or deleting?
For example:
class A(object):
def __init__(self):
self.attribute = set()
def __add__(self, other):
self.attribute.add(other)
return self
def __sub__(self, other):
self.attribute.remove(other)
return self
@property
def stuff(self):
return self.attribute
It feels semantically correct to use a property and the right error messages are raised if an attempt to set or delete the property occurs AttributeError: can't set attribute
. But, it also feels like a bit of a con to potential users who, knowing that a property exists, may expect the rest of the contract (setting and deleting) to be fulfilled.
Another option might be to define the setter/deleter in terms of __add__
and __sub__
but this seems like a less obvious breach of the same contract.
@stuff.setter
def stuff(self, other):
self.__add__(other)
Best Answer
Read-only properties are perfectly fine. They are one of the very few Python mechanisms to offer some kind of encapsulation.
It is not terribly important whether you use a property or a getter method, as the difference is purely syntactic. However, they suggest slightly different semantics: a property should be field-like and provide you direct access to a value (which may be computed on the fly). A method may be more involved, and has the opportunity to receive additional parameters.
It is best not to put excessively clever behaviour into your properties, such as appending upon assigning. That just invites difficult to debug problems, as the contract of this property is not obvious. In fact, any kind of operator overloading should be looked at carefully. E.g. in
foo(a + x)
wherea = A()
, you would be modifying thea
object! That is quite unexpected. You might want to overload__iadd__
instead, so that you must usea += x; foo(a)
. If you want a fluent interface so that you can modify your object in an expression, then maybe prefer a method name with a telling name that suggests in-place modification, e.g.foo(a.add(x))
.