Public attributes with really private data

Mark Summerfield list at qtrac.plus.com
Fri May 8 10:48:59 EDT 2009


On 8 May, 13:56, Peter Otten <__pete... at web.de> wrote:
> Mark Summerfield wrote:
> > On 8 May, 08:19, Peter Otten <__pete... at web.de> wrote:
> >> MarkSummerfieldwrote:
> >> > 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.
>
> >> Do really you think what you suggest below is "nice"?
>
> > Well the code isn't ugly and doesn't mess with the call stack etc.
>
> >> By the way, your Attribute descriptor stores the value for all instances
> >> of A in the same variable...
>
> > It seems like it does, but it doesn't. The hidden_value is an instance
> > variable that is created every time an Attribute object is created.
>
> >>>> from Attribute import *
> >>>> class A:
> > a = Attribute("a", 5, lambda *a: True)
> > b = Attribute("b", 5, lambda *a: True)
> >>>> class B:
> > a = Attribute("a", 5, lambda *a: True)
> > b = Attribute("b", 5, lambda *a: True)
> >>>> a = A()
> >>>> b = B()
> >>>> a.a,a.b,b.a,b.b
> > (5, 5, 5, 5)
> >>>> a.a=1;a.b=2;b.a=3;b.b=4
> >>>> a.a,a.b,b.a,b.b
> > (1, 2, 3, 4)
>
> But attribute values are shared between all instances of the same class:
>
> >>> class A:
>
> ...     x = Attribute("x", 42, lambda *a: True)
> ...>>> a = A()
> >>> b = A()
> >>> a.x, b.x
> (42, 42)
> >>> a.x = "yadda"
> >>> a.x, b.x
>
> ('yadda', 'yadda')
>
> Peter

OK, I couldn't quite give it up. But the solution isn't nice or good.
It does work, but at the cost of an extra dictionary lookup on every
get or set, plus a dictionary to hold all the getters & setters. I
_don't recommend it_, but here it is anyway. I've done with it now:-)

class Attribute:

    __accessors = {}

    def __init__(self, name, first_value=None, validator=None):
        self.name = name
        self.first_value = first_value
        self.validator = validator

    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        if (id(instance), self.name) not in self.__accessors:
            self.__makeAccessors(instance)
        return self.__accessors[id(instance), self.name][0](instance)


    def __set__(self, instance, value):
        if (id(instance), self.name) not in self.__accessors:
            self.__makeAccessors(instance)
        setter = self.__accessors[id(instance), self.name][1]
        if setter is None:
            raise AttributeError("'{0}' is read-only".format(
                                 self.__name__))
        return setter(instance, value)


    def __makeAccessors(self, instance):
        hidden_value = self.first_value
        getter = lambda self: hidden_value
        if self.validator is not None:
            def set(instance, value):
                if self.validator(instance, value):
                    nonlocal hidden_value
                    hidden_value = value
                else:
                    raise ValueError("'{0}' is not valid for {1}"
                                     .format(value, name))
            setter = set
        else:
            setter = None
        self.__accessors[id(instance), self.name] = (getter, setter)

--
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