Почему ключевое слово is ведет себя иначе, когда в строке есть точка?

Рассмотрим этот код:

>>> x = "google"
>>> x is "google"
True
>>> x = "google.com"
>>> x is "google.com"
False
>>>

Почему это так?

Чтобы убедиться в правильности вышеизложенного, я только что протестировал Python 2.5.4, 2.6.5, 2.7b2, Python 3.1 для Windows и Python 2.7b1 для Linux.

Похоже, что во всех них есть последовательность, так что это задумано. Я что-то пропустил?

Я только что узнал об этом из-за того, что некоторые из моих личных скриптов фильтрации доменов не справились с этим.


person YOU    schedule 18.05.2010    source источник
comment
Хорошо поймал. Это странно.   -  person Adam Crossland    schedule 18.05.2010
comment
Оба False в Python 2.5.2.   -  person Håvard S    schedule 18.05.2010
comment
В ActivePython 2.5.4.4 видны те же результаты, что и в OP. Почти наверняка это как-то связано с интернированием строк, верно?   -  person Adam Crossland    schedule 18.05.2010
comment
Связано: оператор Python неожиданно ведет себя с целыми числами   -  person ire_and_curses    schedule 18.05.2010
comment
Спасибо, @ire, похоже, я неправильно использую оператор is!   -  person YOU    schedule 18.05.2010
comment
возможный дубликат Python '==' vs 'is', сравнивающий строки , 'is' иногда не работает, почему?   -  person SilentGhost    schedule 18.05.2010


Ответы (2)


is проверяет идентичность объекта, и любая реализация Python, когда она встречает литералы неизменяемых типов, совершенно свободна либо создать новый объект этого неизменяемого типа, или искать существующие объекты этого типа, чтобы увидеть, можно ли повторно использовать некоторые из них (путем добавления новой ссылки на тот же базовый объект). Это прагматичный выбор оптимизации, и он не подвержен семантическим ограничениям, поэтому ваш код никогда не должен полагаться на то, какой путь может выбрать конкретная реализация (иначе он может выйти из строя из-за выпуска исправления/оптимизации Python!) .

Рассмотрим, например:

>>> import dis
>>> def f():
...   x = 'google.com'
...   return x is 'google.com'
... 
>>> dis.dis(f)
  2           0 LOAD_CONST               1 ('google.com')
              3 STORE_FAST               0 (x)

  3           6 LOAD_FAST                0 (x)
              9 LOAD_CONST               1 ('google.com')
             12 COMPARE_OP               8 (is)
             15 RETURN_VALUE    

поэтому в этой конкретной реализации, внутри функции, ваше наблюдение неприменимо, и для литерала (любого литерала) создается только один объект, и действительно:

>>> f()
True

Прагматически это связано с тем, что внутри функции, выполняющей проход через локальную таблицу констант (чтобы сэкономить память, не создавая несколько постоянных неизменяемых объектов там, где достаточно одного), это довольно дешево и быстро и может обеспечить хорошие результаты производительности, поскольку функция может вызываться многократно после.

Но та же самая реализация, в интерактивной подсказке (Правка: изначально я думал, что это также произойдет на верхнем уровне модуля, но комментарий @Thomas меня правильно , увидим позже):

>>> x = 'google.com'
>>> y = 'google.com'
>>> id(x), id(y)
(4213000, 4290864)

НЕ беспокоится о том, чтобы таким образом экономить память - id разные, т. Е. Отдельные объекты. Есть потенциально более высокие затраты и более низкие доходы, поэтому эвристика оптимизатора этой реализации говорит ему не беспокоиться о поиске и просто идти вперед.

Изменить: на верхнем уровне модуля, согласно наблюдению @Thomas, например:

$ cat aaa.py
x = 'google.com'
y = 'google.com'
print id(x), id(y)

снова мы видим оптимизацию памяти на основе таблицы констант в этой реализации:

>>> import aaa
4291104 4291104

(конец редактирования по наблюдению @Thomas).

Наконец, снова в той же реализации:

>>> x = 'google'
>>> y = 'google'
>>> id(x), id(y)
(2484672, 2484672)

эвристика здесь отличается, потому что литеральная строка «выглядит так, как будто это может быть идентификатор» - поэтому она может использоваться в операции, требующей интернирования... поэтому оптимизатор все равно интернирует ее (и после интернирования поиск становится очень быстрым из курс). И действительно, сюрприз-сюрприз...:

>>> z = intern(x)
>>> id(z)
2484672

...x был interned в самый первый раз (как видите, возвращаемое значение intern является тем же объектом, что и x и y, так как он имеет тот же id() ). Конечно, вы также не должны полагаться на это - оптимизатор не должен интернировать что-либо автоматически, это просто эвристика оптимизации; если вам нужна строка interned, intern укажите их явно, на всякий случай. Когда вы выполняете внутренние строки явно...:

>>> x = intern('google.com')
>>> y = intern('google.com')
>>> id(x), id(y)
(4213000, 4213000)

...затем вы делаете гарантируете, что один и тот же объект (т.е. одно и то же id()) будет получаться каждый раз, поэтому вы можете применить микрооптимизацию, например проверку с помощью is, а не == (я вряд ли когда-либо обнаруживал, что незначительный прирост производительности стоит того;-).

Изменить: чтобы уточнить, вот о каких различиях в производительности я говорю на медленном Macbook Air...:

$ python -mtimeit -s"a='google';b='google'" 'a==b'
10000000 loops, best of 3: 0.132 usec per loop
$ python -mtimeit -s"a='google';b='google'" 'a is b'
10000000 loops, best of 3: 0.107 usec per loop
$ python -mtimeit -s"a='goo.gle';b='goo.gle'" 'a==b'
10000000 loops, best of 3: 0.132 usec per loop
$ python -mtimeit -s"a='google';b='google'" 'a is b'
10000000 loops, best of 3: 0.106 usec per loop
$ python -mtimeit -s"a=intern('goo.gle');b=intern('goo.gle')" 'a is b'
10000000 loops, best of 3: 0.0966 usec per loop
$ python -mtimeit -s"a=intern('goo.gle');b=intern('goo.gle')" 'a == b'
10000000 loops, best of 3: 0.126 usec per loop

...не более нескольких десятков наносекунд в любом случае. Так что стоит даже задуматься только в самых экстремальных ситуациях «оптимизировать [ругательство удалено] из этого [ругательство удалено] узкого места производительности»!-)

person Alex Martelli    schedule 18.05.2010
comment
Похоже, я неправильно использую оператор is - person YOU; 18.05.2010
comment
@ S.Mark, возможно, но не обязательно - см. мою правку о стажировке. Обычно вы должны использовать is только для изменчивых элементов, таких как списки, и синглтонов, таких как None, но если вы обеспечили интернирование (как действительно микроскопическую оптимизацию), вы также можете использовать его там (интернирование также делает == проверки немного быстрее, поэтому вам может не понадобиться вставлять is, даже если вы делаете неукоснительно интернируете все соответствующие строки!-). - person Alex Martelli; 18.05.2010
comment
Алекс, вы говорите на верхнем уровне модуля (или интерактивной подсказке), но я считаю, что то, что вы описываете (и то, что видел ОП), только происходит в интерактивной подсказке - верхний уровень модуля все еще компилируется в один объект кода, и все ссылки на одну и ту же константу в этом объекте кода используют одну и ту же ссылку. - person Thomas Wouters; 18.05.2010
comment
@ Томас, ты прав - я слишком долго не смотрел на эти конкретные внутренности. Спасибо! Позвольте мне отредактировать, чтобы исправить... - person Alex Martelli; 18.05.2010
comment
Поведение интерактивной подсказки в отношении того, что строка выглядит как идентификатор, относится только к литеральным строкам. В строках, созданных выражениями, происходит действительно странное поведение. В Python 2.6 и 3.2, например: >>> 'a'*20 is 'a'*20 True >>> 'a'*21 is 'a'*21 False - person Mitchell Model; 09.06.2011

"есть" - это проверка личности. Python имеет некоторое поведение кэширования для небольших целых чисел и (по-видимому) строк. «is» лучше всего использовать для одноэлементного тестирования (например, None).

>>> x = "google"
>>> x is "google"
True
>>> id(x)
32553984L
>>> id("google")
32553984L
>>> x = "google.com"
>>> x is "google.com"
False
>>> id(x)
32649320L
>>> id("google.com")
37787888L
person Jeremy Brown    schedule 18.05.2010