Why is this false? `SomeClass.method is SomeClass.

2019-01-20 04:55发布

问题:

Take this code for example:

class SomeClass():
    def a_method(self):
        pass

print(SomeClass.a_method is SomeClass.a_method)     # Example 1: False
print(SomeClass.a_method == SomeClass.a_method)     # Example 2: True
print(SomeClass().a_method is SomeClass().a_method) # Example 3: False
print(SomeClass().a_method == SomeClass().a_method) # Example 4: False
  • Example 1: I would have guessed that they were the same object. Does Python make a copy of the method each time it is referenced?
  • Example 2: Expected.
  • Example 3: Expected, since they are different objects.
  • Example 4: Why doesn't this output match example 2?

回答1:

Example 1:

Someclass.a_method is an unbound method. These don't even exist in Python nowadays, so consider this a useless history lesson.

Does Python make a copy of the method each time it is referenced?

Yes, more or less. This is done via descriptor protocol.

>>> SomeClass.a_method  # unbound method via attribute access
<unbound method SomeClass.a_method>
>>> SomeClass.__dict__['a_method']  # just stored as a function in the class dict
<function __main__.a_method>
>>> SomeClass.__dict__['a_method'].__get__(None, SomeClass)
<unbound method SomeClass.a_method>

The last line is showing the "binding" operation that descriptors invoke for attribute access on a class, but written out manually. In pure Python, it's something like this

class Function(object):
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        return types.MethodType(self, obj, objtype):

You can also create a bound method that way:

>>> some_instance = SomeClass()
>>> SomeClass.__dict__['a_method'].__get__(some_instance, SomeClass)
<bound method SomeClass.a_method of <__main__.SomeClass instance at 0xcafef00d>>

Example 2:

Method comparison is done via the __func__ and __self__ attributes on the methods. In this case, they are both identical: the __func__ is the same plain old function you can dig out of the class dict, and the __self__ is None. So despite these methods being different objects, they compare equal.

Example 3:

Correct. They are different objects, and hence not identical.

Example 4:

As mentioned earlier, comparison is using the __func__ and __self__ attributes. The result doesn't match example 2 because, in this case, the __self__ attributes are referring to different instances. Those different instances don't compare equal because SomeClass instances compare by identity, therefore the methods also don't compare equal.

A final note on the current version of Python

Everything mentioned above applies to the current version of the language, too, except for Example 1. In Python, there is no longer such thing as an unbound method, this unnecessary complication in the object model was removed.

>>> SomeClass.a_method
<function __main__.SomeClass.a_method(self)>
>>> SomeClass.a_method is SomeClass.__dict__['a_method']
True

What was an "unbound method" in Python 2 is now just a plain old function, and the instance retrieved via attribute access is identical to the object in the class dict. The Example 1 result changes from False to True in a Python 2 -> Python 3 upgrade.