Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python - call instance method using __func__

Tags:

python

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.

like image 638
cutejumper Avatar asked Oct 17 '12 13:10

cutejumper


People also ask

How do you call an instance method in Python?

Calling An Instance Method operator to execute the block of code or action defined in the instance method. First, create instance variables name and age in the Student class. Next, create an instance method display() to print student name and age. Next, create object of a Student class to call the instance method.

What is __ func __ in Python?

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.

What is __ call __ method in Python?

The __call__ method enables Python programmers to write classes where the instances behave like functions and can be called like a function. When the instance is called as a function; if this method is defined, x(arg1, arg2, ...) is a shorthand for x. __call__(arg1, arg2, ...) .

What is __ class __ in Python?

__class__ # Output: <class 'type'> Thus, the above code shows that the Human class and every other class in Python are objects of the class type . This type is a class and is different from the type function that returns the type of object.


1 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_METHOD - CALL_METHOD opcode pair that replaces the current LOAD_ATTRIBUTE - CALL_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.

like image 106
Martijn Pieters Avatar answered Sep 20 '22 16:09

Martijn Pieters