5

我发现以下示例有点令人惊讶:

>>> class Foo:
        def blah(self):
            pass


>>> f = Foo()
>>> def bar(self):
        pass

>>> Foo.bar = bar
>>> f.bar
<bound method Foo.bar of <__main__.Foo object at 0x02D18FB0>>

我希望绑定方法与每个特定实例相关联,并在构造时放置在其中。似乎合乎逻辑的是,每个实例的绑定方法都必须不同,以便它知道将哪个实例传递给底层函数 - 实际上:

>>> g = Foo()
>>> g.blah is f.blah
False

但是我对这个过程的理解显然是有缺陷的,因为我不希望将函数分配给类属性会将其放入到那时已经创建的实例中。

所以,我的问题有两个——

  1. 为什么将函数分配给类会追溯应用于实例?造成这种情况的实际查找规则和过程是什么?
  2. 这是语言保证的事情,还是只是碰巧发生的事情?
4

2 回答 2

11

你想大吃一惊,试试这个:

f.blah is f.blah

没错,实例方法包装器每次访问都不一样。

实际上,实例方法是一个描述符。换句话说,f.blah实际上是:

Foo.blah.__get__(f, type(f))

方法实际上并不存储在实例上;它们存储在类中,并动态生成方法包装器以将方法绑定到实例。

于 2012-06-16T03:47:36.027 回答
9

实例不“包含”该方法。查找过程在您访问时动态发生foo.bar。它检查实例是否具有该名称的属性。因为它没有,所以它查看类,然后找到该类当时具有的任何属性。请注意,方法在这方面并不特殊。如果你设置,你会看到同样的效果Foo.bar = 2;之后,foo.bar将评估为 2。

该语言保证的是属性查找以这种方式进行:首先是实例,然后是类,如果在实例上找不到属性。(通过运算符重载等隐式调用的特殊方法的查找规则不同。)

只有当您直接将属性分配给实例时,它才会屏蔽类属性。

>>> foo = Foo()
>>> foo.bar
Traceback (most recent call last):
  File "<pyshell#79>", line 1, in <module>
    foo.bar
AttributeError: 'Foo' object has no attribute 'bar'
>>> foo.bar = 2
>>> Foo.bar = 88
>>> foo.bar
2

以上所有内容与绑定/未绑定方法是分开的。Python 中的类机制使用描述符协议,因此当您访问时foo.bar,会动态创建一个新的绑定方法实例。这就是为什么您会在不同的对象上看到不同的绑定方法实例。但请注意,这些绑定方法在底层依赖于相同的代码对象,正如您在类中编写的方法所定义的那样:

>>> foo = Foo()
>>> foo2 = Foo()
>>> foo.blah.im_func.__code__ is foo2.blah.im_func.__code__
True
于 2012-06-16T03:48:32.063 回答