Оператор is ведет себя по-разному при сравнении строк с пробелами.

Я начал изучать Python (python 3.3) и пробовал оператор is. Я пробовал это:

>>> b = 'is it the space?'
>>> a = 'is it the space?'
>>> a is b
False
>>> c = 'isitthespace'
>>> d = 'isitthespace'
>>> c is d
True
>>> e = 'isitthespace?'
>>> f = 'isitthespace?'
>>> e is f
False

Кажется, что пробел и вопросительный знак заставляют is вести себя по-разному. Что происходит?

EDIT: я знаю, что должен использовать ==, я просто хотел знать, почему is ведет себя так.


person luisdaniel    schedule 26.05.2013    source источник
comment
Для записи вы должны использовать == для сравнения любого элемента на равенство, но, тем не менее, это интересный вопрос.   -  person jamylak    schedule 26.05.2013
comment
Вероятно, какое-то интернирование строк вызывает a is b (обратите внимание, что строковая константа, назначенная b, уже создана и повторно используется). Правило интернирования должно учитывать пробелы (или, возможно, длину)   -  person Ben Jackson    schedule 26.05.2013
comment
Хм... У меня разные результаты при использовании файла вместо записи в интерпретаторе. То же самое в ideone.   -  person awesoon    schedule 26.05.2013
comment
По какой-то причине id('ab') постоянно возвращает одно и то же значение в моей оболочке, а id('a ') постоянно изменяется. Я до сих пор понятия не имею, почему буквы ведут себя иначе, но наблюдать за этим интересно. Возможно, Python делает какую-то оптимизацию, предполагая, что строки часто содержат буквы? Я не думаю, что это имело бы большой смысл, но трудно объяснить такое поведение. Это интересный вопрос.   -  person Nolen Royalty    schedule 26.05.2013
comment
Я все еще хотел бы увидеть окончательный ответ на этот вопрос относительно CPython   -  person jamylak    schedule 26.05.2013
comment
Как вы уже знаете о том, что на самом деле делает is, возможно, этот вопрос было бы полезно, если бы оно содержало полезный ответ.   -  person glglgl    schedule 26.05.2013
comment
прочитайте это stackoverflow.com/questions/11476190/why-0-6-is-6- ложь   -  person Dmitry Zagorulkin    schedule 26.05.2013


Ответы (5)


Внимание! Этот ответ касается деталей реализации конкретного интерпретатора Python. сравнение строк с is==плохой идеей.

Ну, по крайней мере, для cpython3.4/2.7.3 ответ "нет, это не пробел". Не только пробел:

  • Два строковых литерала будут совместно использовать память, если они либо буквенно-цифровые, либо находятся в одном и том же блоке (файле, функции, классе или одной команде интерпретатора).

  • Выражение, результатом которого является строка, приведет к созданию объекта, идентичного объекту, созданному с использованием строкового литерала, тогда и только тогда, когда он создан с использованием констант и бинарных/унарных операторов, а результирующая строка короче 21 символа.

  • Отдельные символы уникальны.

Примеры

Буквенно-цифровые строковые литералы всегда совместно используют память:

>>> x='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
>>> y='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
>>> x is y
True

Строковые литералы, не являющиеся буквенно-цифровыми, совместно используют память тогда и только тогда, когда они совместно используют окружающий синтаксический блок:

(устный переводчик)

>>> x='`!@#$%^&*() \][=-. >:"?<a'; y='`!@#$%^&*() \][=-. >:"?<a';
>>> z='`!@#$%^&*() \][=-. >:"?<a';
>>> x is y
True 
>>> x is z
False 

(файл)

x='`!@#$%^&*() \][=-. >:"?<a';
y='`!@#$%^&*() \][=-. >:"?<a';
z=(lambda : '`!@#$%^&*() \][=-. >:"?<a')()
print(x is y)
print(x is z)

Выход: True и False

Для простых бинарных операций компилятор выполняет очень простое распространение констант (см. peephole.c). ), но со строками это происходит только в том случае, если результирующая строка короче 21 символа. В этом случае действуют упомянутые ранее правила:

>>> 'a'*10+'a'*10 is 'a'*20
True
>>> 'a'*21 is 'a'*21
False
>>> 'aaaaaaaaaaaaaaaaaaaaa' is 'aaaaaaaa' + 'aaaaaaaaaaaaa'
False
>>> t=2; 'a'*t is 'aa'
False
>>> 'a'.__add__('a') is 'aa'
False
>>> x='a' ; x+='a'; x is 'aa'
False

Конечно, отдельные символы всегда разделяют память:

>>> chr(0x20) is ' '
True
person Community    schedule 26.05.2013
comment
Вероятно, тогда строки не интернируются, как я думал раньше, а берутся - как строковые литералы - из того же пула строк внутри модуля. - person glglgl; 26.05.2013
comment
Возможно, что когда вы жестко кодируете 'a'*20 (или добавляете строковые литералы), интерпретатор принимает решение об оптимизации и заменяет его результирующей строкой 'aaaaaaaaaaaaaaaaaaaa', поэтому во время выполнения не выполняется никаких манипуляций со строками (более быстрое выполнение, но больший скомпилированный код) ; но когда умножение слишком велико, оптимизация не активируется, чтобы сохранить небольшой размер кода. - person Iftah; 26.05.2013
comment
@lftah dis.dis(lambda: 'x'*20) приводит к LOAD_CONST 3 ('xxxxxxxxxxxxxxxxxxxx'), dis.dis(lambda: 'aaaaa' + "aaa") к LOAD_CONST 3 ('aaaaaaaa'). Я мог представить, что вызов метода (например, .join() слишком сложен в этом смысле. - person glglgl; 27.05.2013
comment
Кстати, dis.dis(lambda: 'x'*21) ведет к LOAD_CONST 1 ('x') LOAD_CONST 2 (21) BINARY_MULTIPLY - person glglgl; 27.05.2013
comment
Похоже, что компилятор выполняет простое распространение констант, включая «*» или «+», если результат меньше 21 символа. - person Elazar; 27.05.2013
comment
То, что вы показываете, - это только то, как ведет себя эта конкретная реализация. Другими словами, это всего лишь деталь реализации, оптимизация, и ее нельзя считать особенностью языка. Подумайте о ситуации, когда алгоритм должен быть распространен. Нельзя злоупотреблять оператором is только из-за особенностей реализации. - person pepr; 27.05.2013
comment
@pepr, мы уже проходили через это. Здесь интересны детали реализации. Мы не пытаемся научить, как правильно использовать python прямо сейчас. - person Elazar; 27.05.2013
comment
@Элазар: Понятно. Тогда не лучше ли заглянуть внутрь реализации? - person pepr; 27.05.2013
comment
@pepr, вы можете заглянуть внутрь и рассказать нам. Мне было легче стучать по стенам; а вообще - вперед. Заглянуть внутрь реализации - действительно правильный способ сделать это. - person Elazar; 27.05.2013
comment
+1 отличный ответ. Если кто-то использует оболочку Ipython, тогда x='`!@#$%^&*() \][=-. >:"?<a'; y='`!@#$%^&*() \][=-. >:"?<a'; вернет False. - person Ashwini Chaudhary; 27.05.2013
comment
@Elazar: Отличный ответ +1 :) Где вы нашли эту информацию. В каком исходном файле это реализовано? - person aldeb; 20.04.2015
comment
Спасибо. В ответе есть ссылка. Но вообще я просто играл с REPL. - person Elazar; 20.04.2015

Чтобы немного расширить ответ Игнасио: оператор is является оператором идентификации. Он используется для сравнения идентичности объекта. Если вы создаете два объекта с одинаковым содержимым, то, как правило, идентичность объекта не дает истинного значения. Это работает для некоторых небольших строк, поскольку CPython, эталонная реализация Python, хранит содержимое отдельно, заставляя все эти объекты ссылаться на одно и то же строковое содержимое. Таким образом, оператор is возвращает true для них.

Однако это деталь реализации CPython и, как правило, не гарантируется ни для CPython, ни для какой-либо другой реализации. Так что использовать этот факт — плохая идея, так как он может сломаться в любой другой день.

Для сравнения строк используется оператор ==, который сравнивает равенство объектов. Два строковых объекта считаются равными, если они содержат одинаковые символы. Таким образом, это правильный оператор для использования при сравнении строк, и is обычно следует избегать, если вы не хотите явно указывать идентификацию объекта (пример: a is False).


Если вас действительно интересуют подробности, вы можете найти реализацию строк CPython здесь. Но опять же: это детали реализации, поэтому вы не должны никогда требовать, чтобы это работало.

person poke    schedule 26.05.2013
comment
Любой, кто читает это, используя python 2.x, также должен знать, что им не следует использовать a is False, поскольку логические значения не являются синглтонами. - person sapi; 26.05.2013
comment
@sapi даже python3 не должен этого делать - person jamylak; 26.05.2013
comment
a is False не имеет смысла. Правильное написание not a. - person Lennart Regebro; 26.05.2013
comment
a is False прекрасно говорит по-английски. Видимо, это слишком хорошо, чтобы быть правдой :) - person Elazar; 26.05.2013
comment
@LennartRegebro - вы можете сравнить два логических выражения на равенство (в этом случае == верно). - person detly; 26.05.2013
comment
@sapi: как насчет Python 2.5 или новее? Я считаю, что если принять эту минимальную версию, то они будут синглтонами (неподходящее слово, но сойдет); это действительно не так? Можете ли вы привести пример? - person Chris Morgan; 26.05.2013
comment
@ChrisMorgan - введите False = True в оболочку; поэтому сравнивать с True или False вообще не имеет смысла. - person sapi; 26.05.2013
comment
Ах да, я полагаю, что так --- но это само по себе не означает, что is True или is False - плохая идея. Если кто-то делает такие вещи, как присвоение имен False или True, они должны ожидать, что что-то сломается. Если сопоставление тождества с именами True и False некорректно, то и присвоение значений True и False аналогично не корректно. - person Chris Morgan; 26.05.2013
comment
@ChrisMorgan: Тебе следует больше думать об этом. a is False это действительно плохая идея (независимо от того, являются ли False и True переменными, константами или ключевыми словами. С лингвистической/логической точки зрения подумайте о таких выражениях, как if not ambiguous is False then... (ambiguous является неправильным идентификатором сам по себе). a is False - это способ выразить простую истину довольно сложным способом. А это не то, что нужно делать при программировании. - person pepr; 27.05.2013
comment
@pepr: конечно, я знаю, что is True и is False почти никогда не будут правильным способом сделать это, но я возражаю против первоначального способа выражения @sapi, что причина, по которой его не следует использовать, заключается в том, что логические значения не являются синглтоны. - person Chris Morgan; 27.05.2013
comment
да. +1. В любом случае, мы всегда должны идти к сути проблемы. - person pepr; 27.05.2013

Оператор is опирается на функцию id, которая является guaranteed to be unique among simultaneously existing objects.. В частности, id возвращает адрес памяти объекта. Кажется, что CPython имеет согласованные адреса памяти для строк, содержащих только символы a-z и A-Z.

Однако это, по-видимому, только в том случае, когда строка была присвоена переменной:

Здесь идентификатор «foo» и идентификатор a совпадают. a было установлено значение "foo" перед проверкой идентификатора.

>>> a = "foo"
>>> id(a)
4322269384
>>> id("foo")
4322269384

Однако идентификатор «бара» и идентификатор a различаются при проверке идентификатора «бара» до установки a равным «бару».

>>> id("bar")
4322269224
>>> a = "bar"
>>> id(a)
4322268984

Повторная проверка идентификатора "bar" после установки a равным "bar" возвращает тот же идентификатор.

>>> id("bar")
4322268984

Таким образом, кажется, что cPython поддерживает согласованные адреса памяти для строк, содержащих только a-zA-Z, когда эти строки присваиваются переменной. Также вполне возможно, что это зависит от версии: я использую python 2.7.3 на macbook. Другие могут получить совершенно другие результаты.

person Nolen Royalty    schedule 26.05.2013
comment
Я удивлюсь, если это зависит от машины. вы, вероятно, имеете в виду зависимость от версии. - person Elazar; 26.05.2013

Фактически ваш код сводится к сравнению идентификаторов объектов (т.е. их физического адреса). Итак, вместо вашего сравнения:

>>> b = 'is it the space?'
>>> a = 'is it the space?'
>>> a is b
False

Ты можешь сделать:

>>> id(a) == id(b)
False

Но обратите внимание, что если бы a и b были непосредственно в сравнении, это сработало бы.

>>> id('is it the space?') == id('is it the space?')
True

На самом деле в выражении есть разделение между одними и теми же статическими строками. Но в масштабе программы есть только совместное использование словесных строк (так что ни пробелов, ни знаков препинания).

Вы не должны полагаться на это поведение, так как оно нигде не задокументировано и является деталью реализации.

person deufeufeu    schedule 26.05.2013

Оператор 'is' сравнивает фактический объект.

c is d также должно быть ложным. Я предполагаю, что python выполняет некоторую оптимизацию, и в этом случае это тот же объект.

person creack    schedule 26.05.2013
comment
CPython хранит пул часто используемых объектов — короткие строковые литералы и примитивы, такие как целые числа в диапазоне 1-100. Нет причин предполагать, что c is d должно быть ложным. - person Elazar; 26.05.2013
comment
Но, с другой стороны, не следует предполагать, что c is d верно. - person poke; 26.05.2013
comment
@Элазар, моя точка зрения. Пул - это оптимизация, а строки с пробелами просто выходят за рамки. @poke прав, не следует предполагать, что c is d - person creack; 26.05.2013
comment
@jamylak Я имел в виду, по крайней мере, я не знал точного диапазона ... спасибо. - person Elazar; 26.05.2013
comment
@creack, очевидно, ОП спрашивает именно об этих деталях реализации. Я предполагаю, что он знает, чтобы не использовать их. - person Elazar; 26.05.2013