Ответ 1
CPython не обещает интернировать все строки по умолчанию, но на практике многие места в кодовой базе Python действительно используют уже созданные строковые объекты. Многие внутренние компоненты Python используют (эквивалент C) вызов функции sys.intern()
для явного интернирования строк Python, но если вы не нажмете один из этих особых случаев, два идентичных строковых литерала Python будут создавать разные строки.
Python также может свободно использовать области памяти, и Python также оптимизирует неизменяемые литералы, сохраняя их один раз, во время компиляции, с байт-кодом в объектах кода. Python REPL (интерактивный интерпретатор) также хранит самый последний результат выражения в имени _
, что еще больше запутывает.
Таким образом, время от времени будет появляться один и тот же идентификатор.
Выполнение только строки id(<string literal>)
в REPL проходит через несколько шагов:
Строка компилируется, что включает создание константы для строкового объекта:
>>> compile("id('foo')", '<stdin>', 'single').co_consts ('foo', None)
Здесь показаны сохраненные константы с скомпилированным байт-кодом; в этом случае строка
'foo'
и синглтонNone
. На этом этапе можно оптимизировать простые выражения, состоящие из неизменяемых значений, см. примечание по оптимизаторам ниже.При выполнении строка загружается из констант кода, и
id()
возвращает ячейку памяти. Полученное значениеint
привязывается к_
, а также печатается:>>> import dis >>> dis.dis(compile("id('foo')", '<stdin>', 'single')) 1 0 LOAD_NAME 0 (id) 3 LOAD_CONST 0 ('foo') 6 CALL_FUNCTION 1 9 PRINT_EXPR 10 LOAD_CONST 1 (None) 13 RETURN_VALUE
На объект кода ничего не ссылается, счетчик ссылок падает до 0, а объект кода удаляется. Как следствие, строковый объект тоже.
Затем Python может повторно использовать ту же ячейку памяти для нового строкового объекта, если вы повторно запустите тот же код. Обычно это приводит к тому, что при повторении этого кода печатается тот же адрес памяти. Это зависит от того, что еще вы делаете со своей памятью Python.
Повторное использование идентификатора не предсказуемо; если между тем сборщик мусора будет очищать циклические ссылки, можно освободить другую память и получить новые адреса памяти.
Далее, компилятор Python также интернирует любую строку Python, хранящуюся как константу, при условии, что она выглядит достаточно как действительный идентификатор. Функция фабрики объектов кода Python PyCode_New будет интернировать любой строковый объект, содержащий только буквы, цифры или подчеркивания ASCII, путем вызова intern_string_constants()
. Эта функция проходит через структуры констант и для любого найденного там строкового объекта v
выполняет:
if (all_name_chars(v)) {
PyObject *w = v;
PyUnicode_InternInPlace(&v);
if (w != v) {
PyTuple_SET_ITEM(tuple, i, v);
modified = 1;
}
}
где all_name_chars()
задокументировано как
/* all_name_chars(s): true iff s matches [a-zA-Z0-9_]* */
Поскольку вы создали строки, которые соответствуют этому критерию, они интернированы, поэтому вы видите тот же идентификатор, который используется для строки 'so'
во втором тесте: до тех пор, пока сохраняется ссылка на интернированную версию, интернинг будет заставить будущие литералы 'so'
повторно использовать объект внутренней строки, даже в новых блоках кода и привязанных к различным идентификаторам. В первом тесте вы не сохраняете ссылку на строку, поэтому интернированные строки отбрасываются, прежде чем их можно будет использовать повторно.
Кстати, ваше новое имя so = 'so'
связывает строку с именем, содержащим те же символы. Другими словами, вы создаете глобал, имя и значение которого равны. Поскольку Python объединяет и идентификаторы, и квалифицирующие константы, вы в конечном итоге используете один и тот же строковый объект как для идентификатора, так и для его значения:
>>> compile("so = 'so'", '<stdin>', 'single').co_names[0] is compile("so = 'so'", '<stdin>', 'single').co_consts[0]
True
Если вы создаете строки, которые не являются константами объекта кода или содержат символы вне диапазона букв + цифры + подчеркивания, вы увидите, что значение id()
не используется повторно:
>>> some_var = 'Look ma, spaces and punctuation!'
>>> some_other_var = 'Look ma, spaces and punctuation!'
>>> id(some_var)
4493058384
>>> id(some_other_var)
4493058456
>>> foo = 'Concatenating_' + 'also_helps_if_long_enough'
>>> bar = 'Concatenating_' + 'also_helps_if_long_enough'
>>> foo is bar
False
>>> foo == bar
True
Компилятор Python либо использует оптимизатор глазков (версии Python & lt; 3.7), либо более способный оптимизатор AST (версии 3.7 и новее) для предварительного вычисления (свертывания) результатов. простых выражений с участием констант. Портфолдер ограничивает выходной поток последовательностью длиной 20 или менее (для предотвращения раздувания объектов кода и использования памяти), тогда как оптимизатор AST использует отдельное ограничение для строк длиной 4096 символов. Это означает, что объединение более коротких строк, состоящих только из символов имени, может все еще привести к интернированным строкам, если получающаяся строка соответствует ограничениям оптимизатора вашей текущей версии Python.
Например. в Python 3.7 'foo' * 20
приведет к единственной интернированной строке, потому что постоянное сворачивание превращает это в одно значение, в то время как на Python 3.6 или более ранней версии 'foo' * 6
будет свернуто:
>>> import dis, sys
>>> sys.version_info
sys.version_info(major=3, minor=7, micro=4, releaselevel='final', serial=0)
>>> dis.dis("'foo' * 20")
1 0 LOAD_CONST 0 ('foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo')
2 RETURN_VALUE
и
>>> dis.dis("'foo' * 6")
1 0 LOAD_CONST 2 ('foofoofoofoofoofoo')
2 RETURN_VALUE
>>> dis.dis("'foo' * 7")
1 0 LOAD_CONST 0 ('foo')
2 LOAD_CONST 1 (7)
4 BINARY_MULTIPLY
6 RETURN_VALUE