Python - метод вызова вызова с использованием __func__

Я новичок в python, и я не совсем понимаю __func__ в python 2.7.

Я знаю, когда я определяю класс следующим образом:

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

Я могу использовать либо Foo().f('a'), либо Foo.f(Foo(), 'a') для вызова этого метода. Однако этот метод нельзя назвать Foo.f(Foo, 'a'). Но я случайно обнаружил, что могу использовать Foo.f.__func__(Foo, 'a') или даже Foo.f.__func__(1, 'a') для получения того же результата.

Я печатаю значения Foo.f, Foo().f и Foo.f.__func__, и они все разные. Однако в определении есть только один фрагмент кода. Кто может помочь объяснить, как работает код выше, особенно __func__? Теперь я действительно запутался.

Ответы

Ответ 1

При доступе к Foo.f или Foo().f возвращается метод; он несвязан в первом случае и связан со вторым. Метод python по существу является оберткой вокруг функции, которая также содержит ссылку на класс, которым является метод. При привязке он также содержит ссылку на экземпляр.

Когда вы вызываете метод, он проверит проверку первого переданного аргумента, чтобы убедиться, что это экземпляр (он должен быть экземпляром ссылочного класса или подкласса этого класса). Когда метод привязан, он предоставит этот первый аргумент по несвязанному методу, который вы предоставите ему сами.

Этот объект метода имеет атрибут __func__, который является только ссылкой на обернутую функцию. Получая доступ к базовой функции вместо вызова метода, вы удаляете typecheck, и вы можете передать все, что хотите, в качестве первого аргумента. Функции не заботятся о своих типах аргументов, но методы делают.

Обратите внимание, что в Python 3 это изменилось; Foo.f просто возвращает функцию, а не несвязанный метод. Foo().f возвращает метод все еще, все еще привязанный, но больше не существует способа для создания несвязанного метода.

Под капотом каждый функциональный объект имеет __get__ method, это возвращает объект метода:

>>> 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>

Это не самая эффективная кодировка, поэтому в Python 3.7 добавлена ​​новая пара опкод LOAD_METHOD - CALL_METHOD, которая заменяет текущую пару кода LOAD_ATTRIBUTE - CALL_FUNCTION точно, чтобы избежать создания нового объекта метода каждый время. Эта оптимизация преобразует путь выполнения для instance.foo() из type(instance).__dict__['foo'].__get__(instance, type(instance))() с помощью type(instance).__dict__['foo'](instance), поэтому "вручную" передается в экземпляр непосредственно объекту функции. Это экономит около 20% времени на существующих микрообъектах.