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.
When you access
Foo.forFoo().fa 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.fjust returns the function, not an unbound method.Foo().freturns 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:This isn’t the most efficient codepath, so, Python 3.7 adds a new
LOAD_METHOD–CALL_METHODopcode pair that replaces the currentLOAD_ATTRIBUTE–CALL_FUNCTIONopcode pair precisely to avoid creating a new method object each time. This optimisation transforms the executon path forinstance.foo()fromtype(instance).__dict__['foo'].__get__(instance, type(instance))()withtype(instance).__dict__['foo'](instance), so ‘manually’ passing in the instance directly to the function object. This saves about 20% time on existing microbenchmarks.