Ian Bicking: the old part of his blog

Declarative Classes

After thinking about metaclasses and reloading a little bit, it should be clear that the simple approach won't work. You can't update a class (and especially a class's __init__) and expect all the instances to be valid. Maybe you add an instance variable, or take a new argument to the __init__ that specifies the behavior, or cache some values. Because you can't rerun __init__, you can't ensure valid objects.

There are a few solutions to this, all of which require a different style of defining classes than is typical in Python.

One would be to make __init__ idempotent; that is, you can call __init__ multiple times without changing the instance. Presumably you also store the original arguments so you can reconstruct the __init__ call completely. Using something like Michael Hudson's recipe you can keep track of all the instances, and reinitialize them.

Another would be to make changes to __init__ less common. This can be achieved by using higher-level definitions. PEAK is an interesting framework which does some of this. (See the Tutorial (PDF), section 2.2)

In essence you can consider PEAK's bindings to be using declaration in favor of execution. Python has a strong bias to execution. Instances variables aren't declared; you simply create them and they exist. Methods aren't declared; they are just functions attached to classes. Local variables aren't declared, you just start using them. A property isn't declared; you explicitly construct it and bind it. Classes aren't declared; they are created (which is the source of this specific reloading problem).

PEAK's bindings seem to be a way of creating a declared class. Many typical patterns -- patterns which typically would be implemented idiomatically -- are instead turned into explicit structures. For instance, binding.Once (pg. 13) encapsulates a basic memoizing pattern, where you calculate a value lazily then store the value for future accesses. You can implement this idiomatically, perhaps like:

class Car(object):
    def height__get(self):
        try:
            return self.__height
        except AttributeError:
            baseHeight = self.roof.top - self.chassis.bottom
            self.__height = self.wheels.radius + self.baseHeight
            return self.__height
It's not difficult to understand what's happening there by reading the code. But a tool (like a reload-enabling metaclass) cannot understand that code. In the case of reloading and instance variables, we might want an explicit declaration of what variables instances should have, and a default value for that variable. Given that we can fix up old instances.

Of course, reloading is only a very specific case, and not the most interesting case either. Persistence is so "interesting" that it's practically cliche. Publishing, security, interfaces -- these all benefit from declarative structures.

I think the challenge is in making the declarations small (they quickly become a conceptual burden) and dynamic, so that they can reap the benefits of Python.

Created 17 Sep '03
Modified 14 Dec '04