Instance Methods & Cython Functions

One of the great features of Python is the ability to define methods outside of classes. For example, we can define a function which increments the attribute x and add it to a Point class:

def incx(self):
    self.x += 1

class Point(object):
    def __init__(self, x): self.x = x
    incx = incx

We can then create a point at the origin and increment x:

In [2]: p = Point(0)

In [3]: p.incx()

In [4]: print p.x
1

The same code which defines Point continues to work if we move incx to another file, say demo.py, and import it using from demo import incx.

But if we were to put incx in demo.pyx and compile it (using python setup.py build_ext --inplace), we get a strange error when running our simple test:

$ python bad.py
Traceback (most recent call last):
  File "bad.py", line 10, in <module>
    p.incx()
TypeError: incx() takes exactly one argument (0 given)

Read on to learn about instance methods and see how I fixed this.

My first thought was to write a simple wrapper function, using functools.wraps to preserve the docstring and name. This proved to be a clumsy solution.

A better approach turned out to be to use types.MethodType to convert our built-in function into an (unbound) instance method. The docstring for MethodType is pretty short:

instancemethod(function, instance, class)

The first and third arguments are obvious. For the second argument we’ll use None since we have no instance of the class yet, making this an unbound instance method.

So to fix our simple example, we need only replace the incx = incx line in the class definition with a short snippet after the class is defined:

from types import MethodType
Point.incx = MethodType(incx, None, Point)

We can give this new version a try and see that it works:
$ python good.py
1

As always, the entire example is available as a Gist.

Leave a comment