python – call instance method using __func__

Question:

I am new to python, and I don’t quite understand the __func__ in python 2.7.

I know when I define a class like this:

class Foo:
    def f(self, arg):
        print arg

I can use either Foo().f('a') or Foo.f(Foo(), 'a') to call this method. However, I can’t call this method by Foo.f(Foo, 'a'). But I accidently found that I can use Foo.f.__func__(Foo, 'a') or even Foo.f.__func__(1, 'a') to get the same result.

I print out the values of Foo.f, Foo().f and Foo.f.__func__, and they are all different. However, I have only one piece of code in definition. Who can help to explain how above code actually works, especially the __func__? I get really confused now.

Asked By: cutejumper

||

Answers:

When you access Foo.f or Foo().f a method is returned; it’s unbound in the first case and bound in the second. A python method is essentially a wrapper around a function that also holds a reference to the class it is a method of. When bound, it also holds a reference to the instance.

When you call an method, it’ll do a type-check on the first argument passed in to make sure it is an instance (it has to be an instance of the referenced class, or a subclass of that class). When the method is bound, it’ll provide that first argument, on an unbound method you provide it yourself.

It’s this method object that has the __func__ attribute, which is just a reference to the wrapped function. By accessing the underlying function instead of calling the method, you remove the typecheck, and you can pass in anything you want as the first argument. Functions don’t care about their argument types, but methods do.

Note that in Python 3, this has changed; Foo.f just returns the function, not an unbound method. Foo().f returns a method still, still bound, but there is no way to create an unbound method any more.

Under the hood, each function object has a __get__ method, this is what returns the method object:

>>> class Foo(object):
...     def f(self): pass
... 
>>> Foo.f
<unbound method Foo.f>
>>> Foo().f
<bound method Foo.f of <__main__.Foo object at 0x11046bc10>>
>>> Foo.__dict__['f']
<function f at 0x110450230>
>>> Foo.f.__func__
<function f at 0x110450230>
>>> Foo.f.__func__.__get__(Foo(), Foo)
<bound method Foo.f of <__main__.Foo object at 0x11046bc50>>
>>> Foo.f.__func__.__get__(None, Foo)
<unbound method Foo.f>

This isn’t the most efficient codepath, so, Python 3.7 adds a new LOAD_METHODCALL_METHOD opcode pair that replaces the current LOAD_ATTRIBUTECALL_FUNCTION opcode pair precisely to avoid creating a new method object each time. This optimisation transforms the executon path for instance.foo() from type(instance).__dict__['foo'].__get__(instance, type(instance))() with type(instance).__dict__['foo'](instance), so ‘manually’ passing in the instance directly to the function object. This saves about 20% time on existing microbenchmarks.

Answered By: Martijn Pieters
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.