Оператор '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 ведет себя так.

Ответ 1

Предупреждение: этот ответ касается деталей реализации конкретного интерпретатора python. сравнение строк с is == плохая идея.

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

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

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

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

Примеры

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

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

Нетбуквенные строковые литералы обмениваются памятью тогда и только тогда, когда они разделяют охватывающий синтаксический блок:

(интерпретатор)

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

(файл)

x='`[email protected]#$%^&*() \][=-. >:"?<a';
y='`[email protected]#$%^&*() \][=-. >:"?<a';
z=(lambda : '`[email protected]#$%^&*() \][=-. >:"?<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

Ответ 2

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

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

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


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

Ответ 3

Оператор 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

Однако идентификатор "bar" и id a различаются при проверке идентификатора "bar" перед установкой a равной "bar" .

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

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

>>> id("bar")
4322268984

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

Ответ 4

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

>>> 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

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

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

Ответ 5

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

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