Когда вы запрашиваете атрибут экземпляра, который является функцией, вы получаете связанный метод : вызываемый объект, который оборачивает функцию, определенную в классе, и передает экземпляр в качестве первого аргумента.В 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