attributes, properties, and accessors -- philosophy

Bruno Desthuilliers bruno.42.desthuilliers at websiteburo.invalid
Wed Nov 25 10:55:05 CET 2009


Ethan Furman a écrit :
> 
> Let's head towards murkier waters (at least murkier to me -- hopefully 
> they can be easily clarified):  some of the attributes are read-only, 
> such as record count; others are not directly exposed, but still 
> settable, such as table version; and still others require a small amount 
> of processing... at which point do I switch from simple attribute access 
> to method access?

Short answer : you don't !-)

Long answer : well, in fact you do, but the client code doesn't have to 
be aware that it's in fact calling an accessor.

Before we go into more details, you have to know that Python has a 
pretty good support for computed attributes, with both a simple generic 
solution (the property type) and the full monty (custom types 
implementing the descriptor protocol). So from the "interface" POV, you 
should never have an explicit accessor method for what is semantically 
an attribute (wheter the attribute is a plain or a computed one being 
part of the implementation).

Let's start with your second point: "not directly exposed but still 
settable". I assume you mean "not part of the interface, only supposed 
to be accessed (rw) from the methods" - if not, please pardon my 
stupidity and provide better explanations !-). If yes: Python doesn't 
have "language inforced" access restrictions (private / protected / 
etc), but a *very strong* naming convention which is that names starting 
with a leading underscore are implementation details, not part of the 
official interface, and shouldn't be accessed directly. Kind of a 
"warranty voided if unsealed".

So if you have attributes you don't want to "expose" to the outside 
world, just add a single leading underscore to their names.

First and third points are solved by using computed attributes - usually 
a property. The property type takes a few accessor functions as 
arguments - typically, a getter and a setter, and eventually a 
"deleter". Used as a class attribute, a property instance will hook up 
into the attribute lookup / setup mechanism (__getattribute__ and 
__setattr__), and will call resp. it's getter or setter function, 
passing it the instance and (for the setter) value.

This directly solves the third point. For the first one, the obvious 
solution is to use a property with a setter that raises an exception - 
canonically, an AttributeError with a message explaining that the 
attribute is read-only.

And for something more hands-on:

class Person(object):
    def __init__(self, firstname, lastname, birthdate):
        self.firstname = firstname
        self.lastname = lastnale
        self.birthdate = birthdate
        self._foo = 42 # implementation only

    def _getfullname(self):
        return "%s %s" % (self.firstname, self.lastname)
    def _setfullname(self, value):
        raise AttributeError("%s.fullname is read-only" % type(self)
    fullname = property(fget=_getfullname, fset=_setfullname)

    def _getage(self):
        return some_computation_with(self.birthdate)
    def _setage(self, value):
        raise AttributeError("%s.age is read-only" % type(self)
    age = property(fget=_getage, fset=_setage)


For more on computed attributes, you may want to read about the 
"descriptor protocol" (google is your friend as usual). This and the 
attribute resolution mechanism are fundamental parts of Python's inner 
working. Learn how it works if you really want to leverage Python's power.

HTH



More information about the Python-list mailing list