Python method gotcha

Little quirk of Python that drove me crazy.

I love Python decorators. I wanted to make a decorator for lazy evaluation.

class Lazy:

        def __init__(self, f):
                self.f   = f
                self.val = None

        def __call__(self, *a, **b):
                if self.val == None:
                        self.val = self.f(*a, **b)

                return self.val

class A:

        def __init__(self):
                self.a = 5

        @Lazy
        def foo(self):
                print("A.foo")
                return self.a

        def bar(self):
                print("A.bar")
                return self.a

a = A()

a.foo()
a.foo()

Doesn’t work!

Traceback (most recent call last):
  File "demo1.py", line 31, in 
    a.foo()
  File "demo1.py", line 11, in __call__
    self.val = self.f(*a, **b)
TypeError: foo() takes exactly 1 argument (0 given)

What’s going on? It looks like A.foo is not receiving ‘self’. That means it’s not a method — it’s a… callable object.

>>> print(a.foo)
<demo3.Lazy instance at 0x7fc8aed5ddd0>
>>> print(a.bar)
<bound method A.bar of <demo3.A instance at 0x7fc8aed5de18>>

This begs the question — how does Python decide whether something should be

  • A method bound to an object.
  • Just a function defined within a clas.

Well, it turns out, Python looks at the type of objects defined within a class, and if they happen to be a function (not just a callable object), it will make it into a method. A little wrapper fixes the problem.

# -*- coding: utf-8 -*-
def wrap(method):
        def foo(*a, **b):
                return method(*a, **b)

        return foo

def lazy(f):
        return wrap(Lazy(f))
A.foo
A.bar
A.bar

Excellent. Now it’s lazy.

Advertisements
This entry was posted in python. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s