сравнение идентификаторов строк в CPython - PullRequest
4 голосов
/ 31 января 2011

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

if val[2] is not 's':

Я думаю, что это все равно будет работать в любом случае, потому что, насколько я знаю,CPython хранит короткие неизменяемые строки в одном месте.Я заменил его на !=, но мне нужно подтвердить, что данные, которые ранее проходили через этот код, являются правильными, поэтому я хотел бы знать, всегда ли это работало или только иногда работало.

Насколько я знаю, версия Python всегда была 2.6.6, и приведенный выше код, кажется, является единственным местом, где использовался оператор is.

Кто-нибудь знает, будет ли эта строка всегда работать так, как задумал программист?

edit: Поскольку это, без сомнения, очень специфично и бесполезно для будущих читателей, я задам другой вопрос:

Куда мне обратиться, чтобы с полной уверенностью подтвердить поведение реализации Python?Легко ли переварить оптимизацию исходного кода CPython?Любые советы?

Ответы [ 5 ]

3 голосов
/ 31 января 2011

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

В качестве быстрого контрпримера:

import numpy as np

x = 's'
y = np.array(['s'], dtype='|S1')

print x
print y[0]

print 'x is y[0] -->', x is y[0]
print 'x == y[0] -->', x == y[0]

Это дает:

s
s
x is y[0] --> False
x == y[0] --> True

Конечно, если ничто не использовало расширение C любого рода, вы 'Вероятно, это безопасно ... Я бы не рассчитывал на это, хотя ...

Редактировать: В качестве еще более простого примера, он не будет иметь место, если вещи были засолены или упакованы с struct в любомway.

например:

import pickle
x = 's'
pickle.dump(x, file('test', 'w'))
y = pickle.load(file('test', 'r'))

print x is y
print x == y

Также (с использованием другой буквы для ясности, поскольку нам нужно "s" для строки форматирования):

import struct
x = 'a'
y = struct.pack('s', x)

print x is y
print x == y
3 голосов
/ 31 января 2011

Вы можете посмотреть код CPython для 2.6.x: http://svn.python.org/projects/python/branches/release26-maint/Objects/stringobject.c

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

static PyStringObject *characters[UCHAR_MAX + 1];

PyObject *
PyString_FromStringAndSize(const char *str, Py_ssize_t size)
{
    register PyStringObject *op;
    if (size == 1 && str != NULL &&
        (op = characters[*str & UCHAR_MAX]) != NULL)
    {
        Py_INCREF(op);
        return (PyObject *)op;
    }

...
3 голосов
/ 31 января 2011

Вы, конечно, не должны использовать оператор is / is not, когда вы просто хотите сравнить два объекта, не проверяя, совпадают ли эти объекты.

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

2 голосов
/ 31 января 2011

Такое поведение всегда будет применяться к пустым и односимвольным строкам из латинского алфавита.Из unicodeobject.c:

PyObject *PyUnicode_FromUnicode(const Py_UNICODE *u,
                                Py_ssize_t size)
{
.....
    /* Single character Unicode objects in the Latin-1 range are
       shared when using this constructor */
    if (size == 1 && *u < 256) {
        unicode = unicode_latin1[*u];

Этот фрагмент из Python 3, но, вероятно, аналогичная оптимизация существует в более ранних версиях.

0 голосов
/ 28 июня 2013

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

Python - это утка, можно использовать любой объект, похожий на строку, например, тот же код завершается ошибкой, если val[2] на самом деле u"s".

...