Почему метод не идентичен самому себе? - PullRequest
13 голосов
/ 09 января 2011

Документация 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 неверно?

Я использую Python 2.x.Как отмечено в ответах ниже, в Python 3.x C.m is C.m верно.

Ответы [ 3 ]

18 голосов
/ 09 января 2011

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

Я полагаю, вы используете 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 длябольше деталей и, ну, история.

4 голосов
/ 09 января 2011

Поскольку 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: статические методы не похожи на методы класса!

...