简体   繁体   中英

Why is a method not identical to itself?

The Python documentation about the is operator says:

The operators is and is not test for object identity: x is y is true if and only if x and y are the same object. x is not y yields the inverse truth value.

Let's try that:

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

The Python documentation also says :

Due to automatic garbage-collection, free lists, and the dynamic nature of descriptors, you may notice seemingly unusual behaviour in certain uses of the is operator, like those involving comparisons between instance methods, or constants. Check their documentation for more info.

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

I searched for more explanations, but I was not able to find any.

Why is Cm is Cm false?

I am using Python 2.x. As noted in the answers below, in Python 3.x Cm is Cm is true.

When you ask for an attribute of an instance which is a function, you get a bound method : a callable object which wraps the function defined in the class and passes the instance as the first argument. In Python 2.x, when you ask for an attribute of a class which is a function you get a similar proxy object called an unbound method :

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

This special object is created when you ask for it, and not apparently cached anywhere. That means that when you do

>>> A.m is A.m
False

you are creating two distinct unbound method objects and testing them for identity.

Notice that

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

and

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

work fine. ( im_func is the original function which the unbound method object is wrapping.)

In Python 3.x, incidentally, Cm is Cm is True, because the (somewhat pointless) unbound method proxy objects were removed entirely and you just get the original function which you defined.


This is just one example of the very dynamic nature of attribute lookup in Python: when you ask for an attribute of an object, it is possible to run arbitrary Python to calculate the value of that attribute. Here's another example where your test fails in which it is much clearer why:

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

I assume you are using Python 2? In Python 3, Cm is Cm (but C().m is C().m is still false). If you enter just Cm at the REPL, I bet you see something like <UnboundMethod... > . An UnboundMethod wrapper does very little, except checking isinstance(self, cls) . (Seems pretty pointless to create a wrapper for this? It is, so it was dropped in Python 3 - Cm is just a function). A fresh wrapper instance is created on-demand whenever the method is accessed - Cm creates one, another Cm creates another one. Since they're different instances, Cm is not Cm .

Closely related are the bound methods, which allow you to do f = obj.method; f(*args) f = obj.method; f(*args) but also cause instance.method is not instance.method . Upon instanciation, all functions defined in the class (read: all methods, except of course monkeypatched ones) become properties of the instance. When you access them, you instead get a fresh instance of a wrapper (the bound method) around the plain function. This wrapper remembers the instance ( self ) and when called with (arg1, arg2, ..., argN) just hands these on to the function - with self added as first argument. You usually don't notice because you call the method right away - but this is what allows passing self implicitly without resorting to language-level trickery.

See the history of Python for more details and, well, history.

Because Cm() is not a static method of the class C:

Try it like this:

class C:
    @staticmethod
    def m():
        pass

print C.m is C.m
# True

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

Because static methods are like class variables, we only want one reference for them so that if we change their bound value this change should be automatic in all the class and instance of this class.

On the other hand, in your example, Cm is not a static method so Python makes the assumption that it should be treated like a non-static method, so whenever you call Cm , it will return a new instance:

class C:
   def m():
      pass

a = C.m
b = C.m

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

NB: static methods are not like class methods!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM