Read-only attributes via properties versus documentation
In Python I am writing a class where some attributes must remain constant during the lifetime of the object.
One way is to use properties:
class Foo:
def __init__(self, x, y):
'''Create a Foo object
x -- list of int (READ-ONLY)
y -- float
'''
# Save x and y and ensure they are int
self._x = [int(xx) for xx in x]
self.y = float(y)
@property
def x(self):
# Return a copy of self._x to prevent accidental modification
return self._x.copy()
Is this good practice? Should I rely only on the documentation to show which attributes are writable and which are not? Any other suggestion?
source to share
First, note that there is practically no technical way to prevent anyone from accessing your object's implementation and modify it if they really want to.
Now the answer / your question / example:
-
using the implementation attribute (
_x
) and the readonly property makes the intent perfectly clear. -
if your list really needs to be immutable, use
tuple()
. It's semantically controversial (tuple
intended for position-based records - counts relational db records, etc.), not an immutable list), but make sure it will be immutable and avoid having to make copies. -
you still want to clearly document this attribute, which must "remain constant" for the lifetime of the instance.
source to share
You can change your code a little to get at least a complaint
class Foo:
def __init__(self, x, y):
'''Create a Foo object
x -- list of int (READ-ONLY)
y -- float
'''
# Save x and y and ensure they are int
self._x = [int(xx) for xx in x]
self.y = float(y)
@property
def x(self): return self._x
@x.setter
def x(self,value):
raise AttributeError('Don\'t touch my private parts, please')
And then if you try to change the situation:
>>>> a = Foo([1,2,3,4,5],0.2)
>>>> a.x
[1, 2, 3, 4, 5]
>>>> a.x = 3
Traceback (most recent call last):
File "<ipython-input-87-0a024de0ab56>", line 1, in <module>
a.x = 3
File "D:/pydump/gcd_lcm_tests.py", line 230, in x
raise AttributeError('Don\'t touch my private parts, please')
AttributeError: Don't touch my private parts, please
Instead of raising an exception, you can feel free to pass
, but in my opinion it is better to complain.
source to share
I believe you after this:
Example # 1:
from collections import namedtuple
foo = namedtuple("Immutable", ["a", "b"])
Example # 2:
class Foo(object):
__slots__ = ()
In both cases, it is an error to set attributes or change objects in any way.
Demo (s):
>>> from collections import namedtuple
>>> class Foo(object):
... __slots__ = ()
...
>>> Bar = namedtuple("Bar", ["a", "b"])
>>> foo = Foo()
>>> bar = Bar(1, 2)
>>> foo.a = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'a'
>>> foo.a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'a'
>>> bar.a
1
>>> bar.a = 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Note that with an object, namedtuple
you cannot change any of its attributes after they are created. It is very convenient.
source to share
If you want the attribute not to change, don't return a copy on dereferencing: you'll just confuse things further and lead to strange errors. If you want to enforce immutability, turn it into a property and make the setter refuse to modify it.
How well this works depends on the details of your data structure. For example, if your attribute is a list, you can intercept the modification of the list itself, but if it has mutable elements, you can still change their attributes.
source to share