|I l@ve RuBoard|
5.8 Implementing Class Methods
Credit: Thomas Heller
In Python 2.2 (on either classic or new-style classes), the new built-in classmethod function wraps any callable into a class method, and we just bind the same name to the classmethod object in class scope:
class Greeter: def greet(cls, name): print "Hello from %s"%cls._ _name_ _, name greet = classmethod(greet)
In Python 2.1 or earlier, we need a wrapper that is slightly richer than the one used for static methods in Recipe 5.7:
class classmethod: def _ _init_ _(self, func, klass=None): self.func = func self.klass = klass def _ _call_ _(self, *args, **kw): return self.func(self.klass, *args, **kw)
Furthermore, with this solution, the following rebinding is not sufficient:
greet = classmethod(greet)
This leaves greet.klass set to None, and if the class inherited any class methods from its bases, their klass attributes would also be set incorrectly. It's possible to fix this by defining a function to finish preparing a class object and always explicitly calling it right after every class statement. For example:
def arrangeclassmethods(cls): for attribute_name in dir(cls): attribute_value = getattr(cls, attribute_name) if not isinstance(attribute_value, classmethod): continue setattr(cls, classmethod(attribute_value.func, cls))
However, this isn't completely sufficient in Python versions before 2.2, since, in those versions, dir ignored inherited attributes. We need a recursive walk up the bases for the class, as in Recipe 5.3. But a worse problem is that we might forget to call the arrangeclassmethods function on a class object right after its class statement.
For older Python versions, a better solution is possible if you have Jim Fulton's ExtensionClass class. This class is the heart of Zope, so you have it if Zope is installed with Python 2.1 or earlier. If you inherit from ExtensionClass.Base and define a method called _ _class_init_ _, the method is called with the class object as its argument after the class object is built. Therefore:
import ExtensionClass class ClassWithClassMethods(ExtensionClass.Base): def _ _class_init_ _(cls): arrangeclassmethods(cls)
Inherit from ClassWithClassMethods directly or indirectly, and arrangeclassmethods is called automatically on your class when it's built. You still have to write a recursive version of arrangeclassmethods for generality, but at least the problem of forgetting to call it is solved.
Now, with any of these solutions, we can say:
>>> greeting = Greeter( ) >>> greeting.greet("Peter") Hello from Greeter Peter >>> Greeter.greet("Paul") Hello from Greeter Paul
Real class methods, like those in Smalltalk, implicitly receive the actual class as the first parameter and are inherited by subclasses, which can override them. While they can return anything, they are particularly useful as factory methods (i.e., methods that create and return instances of their classes). Python 2.2 supports class methods directly. In earlier releases, you need a wrapper, such as the classmethod class shown in this recipe, and, more problematically, you need to arrange the wrapper objects right after you create a class, so that the objects refer to the actual class when you call them later.
Zope's ExtensionClass helps with the latter part. Metaclasses should also help you achieve the same effect, but, since they were hard to use before Python 2.2, and the likeliest reason to still use Python 2.1 is that you use a version of Zope that requires Python 2.1, this should be avoided. The point is that statements in the class body execute before the class object is created, while our arranging needs to take place after that. Classes that inherit from ExtensionClass.Base solve this problem for us, since their _ _class_init_ _ method automatically executes just after the class object is created, with the class object itself as the only argument. This is an ideal situation for us to delegate to our arrangeclassmethods function.
In Python 2.2, the wrapping inside the class body suffices because the new built-in type classmethod does not need to access the class object at the point of creation, so it's not an issue if the class object does not yet exist when the class methods are wrapped. However, notice that you have to perform the wrapping again if a subclass overrides particular class methods (not, however, if they inherit them).
5.8.4 See Also
|I l@ve RuBoard|