28

我是 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.fFoo().f和的值Foo.f.__func__,它们都是不同的。但是,我只有一段代码在定义中。谁能帮助解释上面的代码实际上是如何工作的,尤其是__func__? 我现在真的很困惑。

4

1 回答 1

54

当您访问Foo.f或返回方法Foo().f时;在第一种情况下它是未绑定的,在第二种情况下是绑定的。python方法本质上是一个函数的包装器,该函数还包含对其作为方法的类的引用。绑定时,它还保存对实例的引用。

当你调用一个方法时,它会对传入的第一个参数进行类型检查,以确保它是一个实例(它必须是被引用类的实例,或者该类的子类)。当方法被绑定时,它会在你自己提供的未绑定方法上提供第一个参数。

正是这个方法对象具有__func__属性,它只是对包装函数的引用。通过访问底层函数而不是调用方法,您可以删除类型检查,并且您可以传入任何您想要的作为第一个参数。函数不关心它们的参数类型,但方法关心。

请注意,在 Python 3 中,这已经改变;Foo.f只返回函数,而不是未绑定的方法。Foo().f返回一个仍然绑定的方法,但是没有办法再创建一个未绑定的方法。

在底层,每个函数对象都有一个__get__方法,这就是返回方法对象的内容:

>>> 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()from type(instance).__dict__['foo'].__get__(instance, type(instance))()with的执行路径type(instance).__dict__['foo'](instance),因此“手动”将实例直接传递给函数对象。这在现有微基准测试上节省了大约 20% 的时间。

于 2012-10-17T13:24:03.983 回答