为什么方法与自身不相同?

时间:2011-01-09 15:12:13

标签: python comparison identity

Python documentation about the is operator说:

  

运算符isis not进行测试   对象标识:如果和,则x is y为真   仅当xy是同一个对象时。 x is not y产生相反的事实   值。

我们试试看:

>>> def m():
...   pass
... 
>>> m is m
True

Python documentation also says

  

由于自动垃圾收集,   免费清单,以及动态性   描述符,你可能会注意到   某些用途中的异常行为   is运算符,就像那些涉及的运算符一样   实例方法之间的比较,   或常数。检查他们的   文档了解更多信息。

>>> class C:
...   def m():
...     pass
... 
>>> C.m is C.m
False

我搜索了更多解释,但我找不到任何解释。

为什么C.m is C.m为假?

我正在使用Python 2.x.如下面的答案中所述,在Python 3.x中C.m is C.m是真的。

3 个答案:

答案 0 :(得分:18)

当你要求一个实例的属性是一个函数时,你得到一个 bound方法:一个可调用的对象,它包装了类中定义的函数,并将实例作为第一个参数传递。在Python 2.x中,当您要求类的属性是一个函数时,您会得到一个类似的代理对象,称为未绑定方法

>>> class A: m = lambda: None
...
>>> A.m
<unbound method A.<lambda>>

这个特殊对象是在你提出要求时创建的,并且在任何地方都没有显示。这意味着当你做

>>> A.m is A.m
False

您正在创建两个不同的未绑定方法对象,并对其进行身份测试。

请注意

>>> x = A.m
>>> x is x
True

>>> A.m.im_func is A.m.im_func
True

工作正常。 (im_func是未绑定方法对象包装的原始函数。)

在Python 3.x中,顺便提一句,C.m is C.m为True,因为完全删除了(有点无意义的)未绑定方法代理对象,您只需获得您定义的原始函数。


这只是Python中属性查找非常动态的一个例子:当你要求对象的属性时,可以运行任意Python来计算该属性的值。这是另一个例子,你的测试失败了,为什么会更清楚:

>>> class ChangingAttribute(object):
...     @property
...     def n(self):
...             self._n += 1
...             return self._n
...
...     def __init__(self):
...             self._n = 0
...
>>> foo = ChangingAttribute()
>>> foo.n
1
>>> foo.n
2
>>> foo.n
3
>>> foo.n is foo.n
False
>>> foo.n
6

答案 1 :(得分:6)

我假设您使用的是Python 2?在Python 3中,C.m is C.m(但C().m is C().m仍然是假的)。如果您在REPL中输入C.m,我打赌您会看到<UnboundMethod... >之类的内容。除了检查isinstance(self, cls)之外,UnboundMethod包装器的功能很少。 (为此创建一个包装器似乎没有意义吗?它是,所以它在Python 3中被删除 - C.m只是一个函数)。每当访问该方法时,都会按需创建一个新的包装器实例 - C.m创建一个,另一个C.m创建另一个。由于它们是不同的实例,C.m is not C.m

绑定方法密切相关,允许您执行f = obj.method; f(*args)但也会导致instance.method is not instance.method。在实例化时,类中定义的所有函数(读取:所有方法,当然除了monkeypatched之外)都成为实例的属性。当您访问它们时,您将获得普通函数周围的包装器(绑定方法)的新实例。这个包装器会记住实例(self),当用(arg1, arg2, ..., argN)调用时,只需将这些操作添加到函数中 - 添加self作为第一个参数。您通常不会注意到因为您立即调用该方法 - 但这是允许隐式传递self而不诉诸语言级欺骗的原因。

有关详细信息,请参阅the history of Python,以及历史记录。

答案 2 :(得分:4)

因为C.m()不是C类的静态方法:

试试这样:

class C:
    @staticmethod
    def m():
        pass

print C.m is C.m
# True

c = C()
print c.m is C.m
# True

因为静态方法就像类变量一样,我们只想要一个引用它们,这样如果我们改变它们的绑定值,这个改变应该在这个类的所有类和实例中是自动的。

另一方面,在您的示例中,C.m不是静态方法,因此Python假设它应该被视为非静态方法,因此每当您调用C.m时,它将返回一个新实例:

class C:
   def m():
      pass

a = C.m
b = C.m

print id(a), id(b)
# 43811616, 43355984
print a is b
# False

N.B:静态方法不像类方法!