Почему метод не идентичен самому себе?
Документация Python относительно оператора is
говорит:
Операторы is
и is not
проверяют Идентификатор объекта: x is y
является истинным, если и только если x
и y
являются одним и тем же объектом. x
is not y
дает обратную истину значение.
Попробуйте следующее:
>>> def m():
... pass
...
>>> m is m
True
Документация Python также говорит:
Благодаря автоматической сборке мусора, бесплатные списки и динамический характер дескрипторы, вы можете заметить, казалось бы, необычное поведение при определенных оператор is
, как и те, которые связаны с сравнения между методами экземпляра, или констант. Проверьте их для дополнительной информации.
>>> class C:
... def m():
... pass
...
>>> C.m is C.m
False
Я искал больше объяснений, но я не смог их найти.
Почему C.m is C.m
false?
Я использую Python 2.x. Как отмечено в ответах ниже, в Python 3.x C.m is C.m
истинно.
Ответы
Ответ 1
Когда вы запрашиваете атрибут экземпляра, который является функцией, вы получаете связанный метод: вызываемый объект, который обертывает функцию, определенную в классе, и передает экземпляр в качестве первого аргумента. В 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
- это исходная функция, которую обтекает объект unbound method.)
В 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
Ответ 2
Я предполагаю, что вы используете Python 2? В Python 3, C.m is C.m
(но C().m is C().m
все еще ложно). Если вы введете только C.m
в REPL, я уверен, вы видите что-то вроде <UnboundMethod... >
. Обертка UnboundMethod делает очень мало, кроме проверки isinstance(self, cls)
. (Кажется довольно бессмысленным создание обертки для этого? Это так, поэтому он был удален в 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
. При инициализации все функции, определенные в классе (читайте: все методы, за исключением, конечно, обезьян.) Становятся свойствами экземпляра. Когда вы обращаетесь к ним, вместо этого вы получаете свежий экземпляр обертки (связанный метод) вокруг простой функции. Эта оболочка запоминает экземпляр (self
), а при вызове с (arg1, arg2, ..., argN)
просто передает эти функции функции - с self
, добавленным в качестве первого аргумента. Обычно вы не замечаете, потому что сразу вызываете метод, но это то, что позволяет пропускать self
неявно, не прибегая к обману уровня языка.
Подробнее см. историю Python, а также историю.
Ответ 3
Поскольку 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: статические методы не похожи на методы класса!