Pythonic attribute magic (property, customized attribute access)

Many languages encourage programmers to use a getter/setter pattern.

Like this:

instance.set_foo(value) print instance.get_foo()

Many years ago, as a junior programmer still drinking the C++ enforced protection kool-aid, I believed this was a good idea. I even used this pattern for data attributes I needed to protect from the hypothetical rogue programmer who would mess around with my classes insides in a way I hadn't intended. Or something like that. Sure, using getters and setters was tedious, verbose, and seemed to be in conflict with the spirit of the Don't Repeat Yourself rule, but surely that was a small price to pay on my way down the Righteous Path of object oriented programming.

Thankfully, like many other programmers in search of improved productivity I eventually moved away from bondage and discipline type programming languages (for most things) and towards high level interpreted languages like Python with a more relaxed attitude. Under this influence many bad OO habits were miraculously cured, including all uses of the getter/setter pattern that didn't involve dynamic code invocation (e.g., get/set data from a database).

Then one day I realized that Python had some magic that could free me forever from the getter/setter pattern. Instead I would just do the simplest possible thing:

instance.foo = value print instance.foo

And behind the scenes the Python magic could implement what looks like a routine attribute access using code of arbitrary complexity that would, for example, serialize value into a file on set, and read it back out from the file and unserialize it on get.

Here is a bit of example code that shows three techniques that can be used to implement Pythonic attributes:

# High-level properties are functional in nature and are easiest to
# use and understand.

class HighLevelProperty(object):
    def __init__(self):
        self._var = None

    def get_var(self):
        print "get_var() => %s" % `self._var`
        return self._var

    def set_var(self, val):
        print "set_var(%s)" % `val`
        self._var = val

    var = property(get_var, set_var)

# Low level property classes give you more control and power in
# exchange for greater verbosity.

class LowLevelProperty(object):
    class Descriptor(object):
        def __get__(self, obj, cls):
            print "__get__(%s, %s) => %s" % (`obj`, `cls`, `obj._var`)
            return obj._var

        def __set__(self, obj, val):
            print "__set__(%s, %s)" % (`obj`, `val`)
            obj._var = val

    def __init__(self):
        self._var = None

    var = Descriptor()

# Customized attribute access is most appropriate when you want
# systematic control over a classes attributes.

class CustomizedAttributeAccess(object):
    # class-level attributes are defined before customized
    # attribute access, which operate at the instance level

    _var = None

    def staticattribute(self):
        print "staticattribute() doesn't go through __getattr__"

    def __getattr__(self, name):
        print "__getattr__(%s)" % `name`
        if name == 'var':
            return object.__getattribute__(self, '_var')

        raise AttributeError("only 'var' is supported")

    def __setattr__(self, name, val):
        print "__setattr__(%s, %s)" % (`name`, `val`)
        if name == 'var':
            object.__setattr__(self, '_var', val)

        raise AttributeError("only 'var' is supported")

I've ordered them by ease of use, but you'll need to use these techniques a bit to get a feel for which is most appropriate under what circumstances.

A good real-world example of Pythonic attribute access is in TKLBAM registry.py module, where I high level functional properties to implement a simple data store object with attributes transparently serialized/unserialized from the filesystem.

Add new comment