When should an attribute be private and made a read-only property? [closed]
I don't know when an attribute should be private and whether I should use property
.
I read recently that setters and getters are not pythonic but using the property
decorator is OK.
But what if I have attribute, that mustn't be set from outside of class but can be read (read-only attribute). Should this attribute be private, and by private I mean with underscore, like that self._x
?
If yes, then how can I read it without using getter?
Only method I know right now is to write
@property
def x(self):
return self._x
That way I can read attribute by obj.x
but I can't set it obj.x = 1
so it's fine.
But should I really care about setting object that mustn't be set? Maybe I should just leave it. But then again I can't use underscore because reading obj._x
is odd for user, so I should use obj.x
and then again user doesn't know that he mustn't set this attribute.
What's your opinion and practices?
Solution 1:
Just my two cents, Silas Ray is on the right track, however I felt like adding an example. ;-)
Python is a type-unsafe language and thus you'll always have to trust the users of your code to use the code like a reasonable (sensible) person.
Per PEP 8:
Use one leading underscore only for non-public methods and instance variables.
To have a 'read-only' property in a class you can make use of the @property
decoration, you'll need to inherit from object
when you do so to make use of the new-style classes.
Example:
>>> class A(object):
... def __init__(self, a):
... self._a = a
...
... @property
... def a(self):
... return self._a
...
>>> a = A('test')
>>> a.a
'test'
>>> a.a = 'pleh'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Solution 2:
Generally, Python programs should be written with the assumption that all users are consenting adults, and thus are responsible for using things correctly themselves. However, in the rare instance where it just does not make sense for an attribute to be settable (such as a derived value, or a value read from some static datasource), the getter-only property is generally the preferred pattern.
Solution 3:
Here is a way to avoid the assumption that
all users are consenting adults, and thus are responsible for using things correctly themselves.
please see my update below
Using @property
, is very verbose e.g.:
class AClassWithManyAttributes:
'''refactored to properties'''
def __init__(a, b, c, d, e ...)
self._a = a
self._b = b
self._c = c
self.d = d
self.e = e
@property
def a(self):
return self._a
@property
def b(self):
return self._b
@property
def c(self):
return self._c
# you get this ... it's long
Using
No underscore: it's a public variable.
One underscore: it's a protected variable.
Two underscores: it's a private variable.
Except the last one, it's a convention. You can still, if you really try hard, access variables with double underscore.
So what do we do? Do we give up on having read only properties in Python?
Behold! read_only_properties
decorator to the rescue!
@read_only_properties('readonly', 'forbidden')
class MyClass(object):
def __init__(self, a, b, c):
self.readonly = a
self.forbidden = b
self.ok = c
m = MyClass(1, 2, 3)
m.ok = 4
# we can re-assign a value to m.ok
# read only access to m.readonly is OK
print(m.ok, m.readonly)
print("This worked...")
# this will explode, and raise AttributeError
m.forbidden = 4
You ask:
Where is
read_only_properties
coming from?
Glad you asked, here is the source for read_only_properties:
def read_only_properties(*attrs):
def class_rebuilder(cls):
"The class decorator"
class NewClass(cls):
"This is the overwritten class"
def __setattr__(self, name, value):
if name not in attrs:
pass
elif name not in self.__dict__:
pass
else:
raise AttributeError("Can't modify {}".format(name))
super().__setattr__(name, value)
return NewClass
return class_rebuilder
update
I never expected this answer will get so much attention. Surprisingly it does. This encouraged me to create a package you can use.
$ pip install read-only-properties
in your python shell:
In [1]: from rop import read_only_properties
In [2]: @read_only_properties('a')
...: class Foo:
...: def __init__(self, a, b):
...: self.a = a
...: self.b = b
...:
In [3]: f=Foo('explodes', 'ok-to-overwrite')
In [4]: f.b = 5
In [5]: f.a = 'boom'
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-5-a5226072b3b4> in <module>()
----> 1 f.a = 'boom'
/home/oznt/.virtualenvs/tracker/lib/python3.5/site-packages/rop.py in __setattr__(self, name, value)
116 pass
117 else:
--> 118 raise AttributeError("Can't touch {}".format(name))
119
120 super().__setattr__(name, value)
AttributeError: Can't touch a