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