I've moved my blog to jmcneil.net. This is no longer being updated!

Sunday, June 29, 2008

Instance Specific Methods

Given an instance of a Python class, it's quite simple to bind a function to that object. One just binds a callable.

Python 2.5.2 (r252:60911, Apr 21 2008, 11:12:42)
[GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class X(object):
... pass
...
>>> def f():
... pass
...
>>> x = X()
>>> x.f
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'X' object has no attribute 'f'
>>> x.f = f
>>> x.f

>>>

The downside here is that when referencing the newly bound attribute, it's not treated as a bound method but rather a standard function object. There are two simple ways to actually create a new bound method that is specific to an instance.

First, one can easily just call the __get__ method of the function object when binding to the instance. Python functions are non-overriding descriptors. When the __get__ method is called, a method object is returned. Various attributes of the new method object handle the object 'plumbing', so to speak: im_self, im_class, im_func. That's really another topic. Here's how it works:

Python 2.5.2 (r252:60911, Apr 21 2008, 11:12:42)
[GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class X(object): pass
...
>>> def f(self): pass
...
>>> x = X()
>>> x.f
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'X' object has no attribute 'f'
>>> x.f = f.__get__(x, X)
>>> x.f
>
>>>

There we go. The 'im_func' is made available as the first argument to the function's __get__ method, as it is really the 'self' parameter that any standard descriptor would be passed.

The next way to make this all happen is via the types module. There is an 'MethodType' object that we can use to wrap a function:

Python 2.5.2 (r252:60911, Apr 21 2008, 11:12:42)
[GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import types
>>> help (types.MethodType)

>>> class X(object): pass
...
>>> def f(self): pass
...
>>> x = X()
>>> x.f
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'X' object has no attribute 'f'
>>> x.f = types.MethodType(f, x, X)
>>> x.f
>
>>> x.f()
>>>

It's my assumption that MethodType is simply calling f.__get__ internally, though I'm slightly too lazy to go look.

No comments: