这里的问题是Python中的方法如何工作.简要地说:
方法只是一个存在于类的命名空间中的函数,当它通过类的instance被访问时,返回一个bound method,它是一个可调用的,实际上是partially applies the instance to the first positional argument to the original function,即它提供self
参数(这是我们通常赋予方法的第一个位置参数的名称),并将实例作为参数.
因此,请考虑:
>>> class Foo:
... def bar(self):
... print("barring", self)
...
>>> Foo.bar
<function Foo.bar at 0x111e16fc0>
当对类本身进行访问时,它只返回函数对象.请注意,在这里,您总是会得到same object,只是在类定义语句体中创建的那个.
>>> Foo.bar is Foo.bar # this will always be True
True
但如果我们创建一个实例,我们会得到一个不同类型的对象:
>>> foo = Foo()
>>> foo.bar
<bound method Foo.bar of <__main__.Foo object at 0x11026cad0>>
>>> type(Foo.bar), type(foo.bar)
(<class 'function'>, <class 'method'>)
>>> foo.bar()
barring <__main__.Foo object at 0x11026cad0>
再次认识到,当通过类访问时,它只是您之前定义的常规函数,它的工作方式与任何其他地方定义的函数一样,它只是存在于类命名空间中:
>>> Foo.bar(42)
baring 42
>>> def baz(self):
... print("bazzing", self)
...
>>> baz(42)
bazzing 42
注意,创建every time you access the object through the instance绑定方法对象.因此,以下各项的计算结果始终为False
:
>>> foo = Foo()
>>> foo.bar is foo.bar # will always be false
False
但是,请注意,如果绑定方法对象将same instance绑定到same function,则它们将是equal.所以:
>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.bar == foo1.bar # always True
True
>>> foo1.bar == foo2.bar # always False
False
此外,这些功能还可以进行内省:
>>> foo.bar.__self__
<__main__.Foo object at 0x11026cad0>
>>> foo.bar.__func__
<function Foo.bar at 0x110250180>
A quick gotcha:
如果您正在遵循REPL,您可能会执行类似以下操作,try 验证我所做的声明是否属实.然后你做这个合理的测试:
>>> foo = Foo()
>>> for _ in range(5):
... print(id(foo.bar))
...
4581348032
4581348032
4581348032
4581348032
4581348032
"嗯?"你可能会问."这是怎么回事,为什么我对据称不同的绑定方法对象得到相同的ID?但当我直接判断时"
>>> foo.bar is foo.bar
False
上面发生的事情是绑定方法获得reclaimed immediately after it is called because it isn't referenced anywhere.因为它是回收的,所以它的生命周期 不会重叠,所以id
并不能保证它们是不同的物体.这是因为在CPython中,id
仅仅是PyObject指针的值,也就是私有托管堆上对象的内存地址.该堆将愉快地重用该内存.但如果你保留一个引用,它不会被回收,所以它被迫使用另一个引用:
>>> foo = Foo()
>>> for _ in range(5):
... bound_method = foo.bar
... print(id(bound_method))
...
4581907008
4581530752
4581907008
4581530752
4581907008
你可以从这个例子中看到类似的现象:
>>> id(object()) == id(object())
True
>>> object() is object()
False
>>> obj1 = object()
>>> obj2 = object()
>>> id(obj1) == id(obj2)
False
这个故事的寓意是,你应该小心使用id
.
†如果您确实想知道这是如何发生的,那么您需要了解描述符.这Descriptor HOWTO本书深入到了细节,值得一读.描述符被Python用来实现核心功能.