【问题标题】:Why does `instance_of_object.foo is instance_of_object.foo` evaluate False? [duplicate]为什么 `instance_of_object.foo is instance_of_object.foo` 评估为 False? [复制]
【发布时间】:2018-05-23 23:19:47
【问题描述】:

如果我有一个

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

所以没有提示......

【问题讨论】:

  • 这不是getattr 的事情; a.foo is not a.foo 也是。
  • @user2357112 为什么不呢?
  • 是的,a.foo is a.fooFalse
  • @101 该链接有帮助,但 GManNickG 的答案更好,IMO。

标签: python introspection


【解决方案1】:

添加到@GManNickG答案:

getattr(a, 'foo').__func__ is a.foo.__func__ 

将返回True

【讨论】:

  • 谢谢,这样更清楚了。
【解决方案2】:

至少在 CPython 中,绑定方法被实现为类 method 的实例。每次请求绑定函数的值时,都会得到该类的 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'>

这个类所做的只是存储对实例的引用和 unbound 函数,当您调用该类时,它会使用存储的实例(以及您的其他参数)调用未绑定函数。

未绑定的函数,如 A.foo,只是常规的旧函数 - 没有构建代理类的新实例,因此身份按预期工作。

造成这种差异的原因是a.foo的语义取决于两个东西,a的值和A.foo的值。为了能够在以后的任何时间点得到这个含义,这两个值都需要存储。这就是method 类的作用。

相反,A.foo 的含义仅取决于单个值:A.foo。所以不需要额外的工作来存储任何东西,并且使用值本身。

您可能会考虑预分配绑定方法实例的想法,以便a.foo 始终返回相同的不可变对象 - 但鉴于 Python 的动态特性,每次只构造一个新对象更简单、更便宜,即使它们可能相同。

【讨论】:

  • 所以多了一层抽象。方法实际上不是类的一部分,而是类method 的实例,而后者又知道函数体和“宿主”类。对吗?
  • mmhh ... 你所说的“常规旧函数”是类“函数”的实例。再说一遍:为什么会有不同的行为?
  • 是的,A.foo 是一个函数,就像任何非类函数一样(例如,比较类型)。行为上的区别在于a.foo 抽象地表示“用a 调用的函数A.foo,因为它们在调用它时都是如此”。为了稍后实际进行此调用,您还必须存储它们的值以供以后使用,method 就是这个存储。简单地引用 A.foo 不需要额外的存储空间,因为您已经有了对唯一值的引用,所以常规的 Python 引用语义就足够了。
  • 感谢@GManNickG 提供了非常好的答案!只需添加/确认 - 请参阅 docs.python.org/2/library/new.html 中的 new.instancemethod
  • 好的,我只是在玩setattr 以及类和类的实例。每次都必须再次生成a.foo,因为A.foo 可能已更改(或other_instance_of_A.foo),我想保持a.foo 与创建a 时一样。为了完整起见,请添加。
【解决方案3】:

存储在类中的一些对象是descriptors,它们不遵循对象查找的常规规则。您在示例中处理的 foo 方法是一种(函数对象是描述符)。

描述符是定义__get__(以及可选的__set____delete__)方法的类的实例。这些方法控制当您在存储描述符的类的实例上查找描述符时会发生什么。

我认为举个例子可以更清楚地说明这一点:

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__ 方法不需要在每次调用时都返回相同的内容。事实上,它通常不会。在函数被用作描述符(即方法)的特定情况下,每次查找函数时都会创建一个新的“绑定方法”对象。因此is 运算符不会将多个绑定方法视为同一个对象,即使它们可能将同一个函数绑定到同一个实例。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2015-03-22
  • 1970-01-01
  • 2015-03-15
  • 2010-11-26
  • 2019-04-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多