Public attributes with really private data
Mark Summerfield
list at qtrac.plus.com
Fri May 8 02:37:03 EDT 2009
Hi,
I had a quick search & didn't find anything _nice_ that produced
attributes with really private data, so I came up with a possible
solution---for Python 3.
(For Python 2 there does seem to be an approach although I'm not
keen on it:
http://www.builderau.com.au/blogs/byteclub/viewblogpost.htm?p=339270875
)
Here's a standard class with one read-only and one writable
property that has a tiny bit of validation.
class P:
def __init__(self, w):
self.w = w
@property
def r(self): return 5
@property
def w(self): return self.__w
@w.setter
def w(self, value):
if value > 0: # Only +ve values allowed
self.__w = value
else:
raise ValueError("'{0}' is not valid for w".format(value))
The read-only property is completely private because it isn't
actually stored as such.
But if we do dir() on an instance, in addition to 'r' and 'w', we
also have '_P__w'. So the writable property's data is easily
accessible, and the validation can be got around:
>>> p = P(9)
>>> p.r, p.w
(5, 9)
>>> p.w = 43
>>> p.r, p.w
(5, 43)
>>> p.w = -7
Traceback (most recent call last):
...
ValueError: '-7' is not valid for w
>>> p._P__w = -7
>>> p.r, p.w
(5, -7)
Here's a class where I can't think of a way to access the private
data and set invalid values.
class A:
r = Attribute("r", 5)
w = Attribute("w", None, lambda self, value: value > 0)
def __init__(self, w):
self.w = w
The Attribute class is a descriptor that takes three arguments:
name of attribute, initial value (essential for read-only
attributes!), and a validator function (which could be "lambda
*a: True" if any value is accepatble).
>>> a = A(9)
>>> a.r, a.w
(5, 9)
>>> a.w = 43
>>> a.r, a.w
(5, 43)
>>> a.w = -7
Traceback (most recent call last):
...
ValueError: '-7' is not valid for w
If we do a dir(a) the only attributes we get (beyond those from
object) are 'r' and 'w', so it shouldn't be possible to get
around the validation---at least not easily.
Here's a rough & ready implementation of the Attribute class:
class Attribute:
def __init__(self, name, first_value=None, validator=None):
self.__name__ = name
hidden_value = first_value
self.__getter = lambda self: hidden_value
if validator is not None:
def set(self, value):
if validator(self, value):
nonlocal hidden_value
hidden_value = value
else:
raise ValueError("'{0}' is not valid for
{1}".format(value, name))
self.__setter = set
else:
self.__setter = None
def __get__(self, instance, owner=None):
if instance is None:
return self
return self.__getter(instance)
def __set__(self, instance, value):
if self.__setter is None:
raise AttributeError("'{0}' is read-only".format(
self.__name__))
return self.__setter(instance, value)
The key to making the attribute data private is that it is held
as part of a closure's state. Notice that nonlocal is needed,
so you need Python 3.
--
Mark Summerfield, Qtrac Ltd, www.qtrac.eu
C++, Python, Qt, PyQt - training and consultancy
"Programming in Python 3" - ISBN 0137129297
More information about the Python-list
mailing list