为什么 `instance_of_object.foo is instance_of_object.foo` 的计算结果为 False?
Why does `instance_of_object.foo is instance_of_object.foo` evaluate False?
如果我有
class A:
def foo(self):
pass
计算结果为 True
:
getattr(A, 'foo') is A.foo
但这计算结果为 False
:
a = A()
getattr(a, 'foo') is a.foo
和
一样
a.foo is a.foo
为什么?
我发现getattr(a, 'foo')
和a.foo
都是用
表示的
<bound method A.foo of <__main__.A object at 0x7a2c4de10d50>>)
所以没有提示....
至少在 CPython 中,绑定方法是作为 class method
的实例实现的。每次请求绑定函数的值时,都会得到此 class.
的 new 实例
x = a.foo
y = a.foo
assert x is not y
id(x) # E.g. 139664144271304
id(y) # E.g. 139664144033992
type(x) # <class 'method'>
type(y) # <class 'method'>
class 所做的只是存储对实例和 unbound 函数的引用,当您调用 class 时,它会调用未绑定函数使用存储的实例(以及您的其他参数)。
未绑定函数,如 A.foo
,只是常规的旧函数 - 没有新的代理实例 class 正在构建,因此身份如您所愿。
造成这种差异的原因是a.foo
的语义取决于两个的东西,a
的值和[=的值12=]。为了能够在以后的任何时间点得到这个含义,这两个值都需要被存储。这就是 method
class 所做的。
相反,A.foo
的含义只取决于一个值:A.foo
。所以不需要额外的工作来存储任何东西,并且使用值本身。
您可能会考虑预分配绑定方法实例的想法,以便 a.foo
始终 returns 相同的不可变对象 - 但考虑到 Python 的动态特性,它是每次只构建一个新的更简单、更便宜,即使它们可能是相同的。
添加到答案:
getattr(a, 'foo').__func__ is a.foo.__func__
将 return True
。
存储在 classes 中的一些对象是 descriptors,它们不遵循对象查找的正常规则。您在示例中处理的 foo
方法是一个(函数对象是描述符)。
描述符是 class 的实例,它定义了 __get__
(以及可选的 __set__
和 __delete__
)方法。这些方法控制当您在存储它的 class 实例上查找描述器时发生的事情。
我想举个例子会更清楚:
class DescriptorClass:
def __get__(*args):
print("__get__ was called")
return "whatever"
class OtherClass:
descriptor_instance = DescriptorClass() # the descriptor instance is a class variable
other_instance = OtherClass()
# this calls the descriptor's __get__ method, which prints "__get__ was called"
result = other_instance.descriptor_instance
print(result) # will print "whatever", since that's what the __get__ method returned
__get__
方法不需要在每次调用时都return 做同样的事情。事实上,它通常不会。在将函数用作描述符(即方法)的特定情况下,每次查找函数时都会创建一个新的 "bound method" 对象。因此 is
运算符不会将多个绑定方法视为同一对象,即使它们可能将相同的函数绑定到同一实例。
如果我有
class A:
def foo(self):
pass
计算结果为 True
:
getattr(A, 'foo') is A.foo
但这计算结果为 False
:
a = A()
getattr(a, 'foo') is a.foo
和
一样a.foo is a.foo
为什么?
我发现getattr(a, 'foo')
和a.foo
都是用
<bound method A.foo of <__main__.A object at 0x7a2c4de10d50>>)
所以没有提示....
至少在 CPython 中,绑定方法是作为 class method
的实例实现的。每次请求绑定函数的值时,都会得到此 class.
x = a.foo
y = a.foo
assert x is not y
id(x) # E.g. 139664144271304
id(y) # E.g. 139664144033992
type(x) # <class 'method'>
type(y) # <class 'method'>
class 所做的只是存储对实例和 unbound 函数的引用,当您调用 class 时,它会调用未绑定函数使用存储的实例(以及您的其他参数)。
未绑定函数,如 A.foo
,只是常规的旧函数 - 没有新的代理实例 class 正在构建,因此身份如您所愿。
造成这种差异的原因是a.foo
的语义取决于两个的东西,a
的值和[=的值12=]。为了能够在以后的任何时间点得到这个含义,这两个值都需要被存储。这就是 method
class 所做的。
相反,A.foo
的含义只取决于一个值:A.foo
。所以不需要额外的工作来存储任何东西,并且使用值本身。
您可能会考虑预分配绑定方法实例的想法,以便 a.foo
始终 returns 相同的不可变对象 - 但考虑到 Python 的动态特性,它是每次只构建一个新的更简单、更便宜,即使它们可能是相同的。
添加到
getattr(a, 'foo').__func__ is a.foo.__func__
将 return True
。
存储在 classes 中的一些对象是 descriptors,它们不遵循对象查找的正常规则。您在示例中处理的 foo
方法是一个(函数对象是描述符)。
描述符是 class 的实例,它定义了 __get__
(以及可选的 __set__
和 __delete__
)方法。这些方法控制当您在存储它的 class 实例上查找描述器时发生的事情。
我想举个例子会更清楚:
class DescriptorClass:
def __get__(*args):
print("__get__ was called")
return "whatever"
class OtherClass:
descriptor_instance = DescriptorClass() # the descriptor instance is a class variable
other_instance = OtherClass()
# this calls the descriptor's __get__ method, which prints "__get__ was called"
result = other_instance.descriptor_instance
print(result) # will print "whatever", since that's what the __get__ method returned
__get__
方法不需要在每次调用时都return 做同样的事情。事实上,它通常不会。在将函数用作描述符(即方法)的特定情况下,每次查找函数时都会创建一个新的 "bound method" 对象。因此 is
运算符不会将多个绑定方法视为同一对象,即使它们可能将相同的函数绑定到同一实例。