2011-01-31 4 views
4

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

if val[2] is not 's': 

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

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

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

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

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

+4

См. Http://stackoverflow.com/questions/2858603/python-why-is-keyword-has-different-behavior-when-there-is-dot-in-the-string/2858680#2858680 для Alex Martelli's погрузиться в детали реализации. Короткий ответ, конечно, «Нет, нет гарантии, что это будет последовательным». С практической точки зрения, вполне вероятно, что с таким коротким строковым литералом, что автоматическое интернирование будет работать, как вы ожидаете. Тем не менее, у меня есть некоторые сомнения в строках, построенных в C-расширениях - они могут не подвергаться тем же самым эвристикам интернирования. –

+0

Так как даже 'chr (115) - это' s'' работает, для которого поиск строки больше влияет на производительность, чем на все, я бы предположил, что поиск происходит автоматически ниже определенной длины. –

+1

Почему бы вам просто не исправить ошибку и не опрокинуть ее? –

ответ

3

Вы можете посмотреть на код 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; 
    } 

... 
+0

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

4

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

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

+1

Я не использовал его, и мы, конечно, больше не будем его использовать. Но мне нужно знать, работает ли это в любом случае или нет. Если нет, то данные ошибочны, и нам нужно что-то сделать, и это будет очень сложно. –

+0

Я думаю, что в CPython он работает, потому что он повторно использует строковые объекты. Не знаю о Jython и т. Д. – ThiefMaster

+0

Вот что я подумал, может кто-нибудь подтвердить это с уверенностью? –

2

Это поведение всегда применяется к пустым и одиночным символам латинских строк. От 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

Замечательно, спасибо! Просматривая исходный код Python2.6 :-) –

3

Как человек уже отмечал, это всегда должно быть верно для строк, созданных в 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 в любом случае.

например:

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

Спасибо за это, я имею дело только со строками, так что все хорошо здесь, но я уверен, что многие другие найдут эту полезную информацию. –

0

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

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