Public attributes with really private data

Mark Summerfield list at qtrac.plus.com
Fri May 8 08:37:03 CEST 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