More Python voodoo: combination class/instance methods

Comments
Bookmark and Share

Id: 86

Here’s a neat tidbit from my website code: Let’s say you’re writing a class, and you want it to have some methods that can be called as either class or instance methods. Of course, you can do it by writing a class method:

class F(object):
    @classmethod
    def foo(cls, a, b, c):
        print a, b, c

F.foo(1, 2, 3)
F().foo(1, 2, 3)

But what if you want a pattern like the following instead, where the method, when called on an instance, can use the instance’s attributes?

F.foo(1, 2, 3)
F(1, 2, 3).foo()

You can do this with a descriptor. Descriptors allow you to customize the way attributes are accessed (and set); you can define precisely what you want F.foo or F().foo to do. This is kind of like the __getattribute__ method, but more general because you can use descriptors for class attributes as well.

class FooDescriptor(object):
    def __init__(self, method_name):
        super(FooDescriptor, self).__init__()
        self.method_name = method_name

    def __get__(self, instance, owner):
        if instance is None:
            # As a class attribute
            def foo(cls, a, b, c):
                print a, b, c
        else:
            # As an instance attribute
            def foo(self):
                print self.a, self.b, self.c
        return foo

The downside of this procedure is that you have to write two different definitions of the method, although that’s generally only a minor inconvenience. If you’re using this technique for a long method, it’s not hard to split out most of the code into a common function that gets called from both the class method and instance method versions.