Рубрики
Без рубрики

Руководство по интернированию строк в Python

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

Автор оригинала: Muhammad Hashir Hassan.

Вступление

Одна из первых вещей, с которой вы сталкиваетесь при изучении основ программирования, – это понятие строк. Подобно различным языкам программирования, строки Python представляют собой массивы байтов, представляющих символы Unicode – массив или последовательность символов. Python, в отличие от многих языков программирования, не имеет отдельного типа данных character , и символы считаются строками длины 1.

Вы можете определить строку, используя одинарные или двойные кавычки, например, a или a . Чтобы получить доступ к определенному элементу строки, вы должны использовать квадратные скобки ( [] ) с индексом символа, к которому вы хотите получить доступ (индексация начинается с 0). Вызов a[0] , например, вернет H .

Тем не менее, давайте взглянем на этот пример кода:

a = 'Hello World'
b = 'Hello World'
c = 'Hello Worl'

print(a is b)
print(a == b)
print(a is c+'d')
print(a == c+'d')

Все строки, которые мы сравниваем , содержат значение Hello World ( a , b и c +'d' ). Интуитивно можно предположить, что вывод будет True для всех этих утверждений.

Однако, когда мы запускаем код, это приводит к:

True
True
False
True

Что может показаться неинтуитивным в этом выводе , так это то, что a-это c + 'd' возвращает False , в то время как очень похожее утверждение a-это b возвращает True . Таким образом, мы можем заключить, что a и b являются одним и тем же объектом, в то время как c – это другой объект, даже если они имеют одно и то же значение.

Если вы не знакомы с Разницей Между is – is проверяет , ссылаются ли переменные на один и тот же объект в памяти , в то время как == проверяет, имеют ли переменные одно и то же значение .

Это различие между a , b и c является продуктом Интернирования строк .

Примечание: Среда, в которой выполняется код, влияет на то, как работает интернирование строк. Предыдущие примеры были результатом запуска кода в виде скрипта в неинтерактивной среде с использованием текущей последней версии Python (версия 3.8.5). Поведение будет отличаться при использовании консоли/Jupiter из-за различных способов оптимизации кода или даже между различными версиями Python.

Это происходит потому, что разные среды имеют разные уровни оптимизации.

Интернирование строк

Строки-это неизменяемые объекты в Python. Это означает, что после того, как строки созданы, мы не можем изменить или обновить их. Даже если кажется, что строка была изменена, под капотом была создана копия с измененным значением и назначена переменной, в то время как исходная строка осталась прежней.

Давайте попробуем изменить строку:

name = 'Wtack Abuse!'
name[0] = 'S'

Поскольку строка name неизменяема, этот код завершится ошибкой в последней строке:

name[0] = 'S'
TypeError: 'str' object does not support item assignment

Примечание: Если вы действительно хотите изменить определенный символ строки, вы можете преобразовать строку в изменяемый объект , такой как list , и изменить нужный элемент:

name = 'Wtack Abuse!'
name = list(name)
name[0] = 'S'
# Converting back to string
name = "".join(name) 

print(name)

Что дает нам желаемый результат:

Stack Abuse!

Причина, по которой мы можем изменить символ в списке (а не в строке), заключается в том, что списки изменчивы – это означает, что мы можем изменить их элементы.

Интернирование строк-это процесс хранения в памяти только одной копии каждого отдельного строкового значения.

Это означает, что, когда мы создаем две строки с одинаковым значением – вместо выделения памяти для них обоих, только одна строка фактически фиксируется в памяти. Другой просто указывает на то же самое место в памяти.

Учитывая эту информацию, давайте вернемся к исходному примеру Hello World :

a = 'Hello World'
b = 'Hello World'
c = 'Hello Worl'

Когда создается строка a , компилятор проверяет, присутствует ли Hello World в памяти Интернета. Поскольку это первое вхождение этого строкового значения, Python создает объект и кэширует эту строку в памяти и указывает a на эту ссылку.

Когда b создается, Hello World находится компилятором в интернет-памяти, поэтому вместо создания другой строки b просто указывает на ранее выделенную память.

строковые значения python в памяти

a-это b и a в данном случае.

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

Когда мы сравниваем a и c+'d' , последний оценивается как Hello World . Однако, поскольку Python не выполняет интернирование во время выполнения, вместо этого создается новый объект. Таким образом, поскольку интернирование не проводилось, эти два не являются одним и тем же объектом и is возвращает False .

В отличие от оператора is оператор == сравнивает значения строк после вычисления runtime expressions – Hello World World .

В это время a и c+'d' являются одинаковыми по значению, поэтому это возвращает True .

Проверка

Давайте посмотрим id созданных нами строковых объектов. Функция id(object) в Python возвращает идентификатор object , который гарантированно будет уникальным в течение всего срока службы указанного объекта. Если две переменные указывают на один и тот же объект, вызов id вернет один и тот же номер:

letter_d = 'd'

a = 'Hello World'
b = 'Hello World'
c = 'Hello Worl' + letter_d
d = 'Hello Worl' + 'd'

print(f"The ID of a: {id(a)}")
print(f"The ID of b: {id(b)}")
print(f"The ID of c: {id(c)}")
print(f"The ID of d: {id(d)}")

Это приводит к:

The ID of a: 16785960
The ID of b: 16785960
The ID of c: 17152424
The ID of d: 16785960

Только c имеет другой идентификатор. Все ссылки теперь указывают на объект с одинаковым значением Hello World . Однако c был вычислен не во время компиляции, а скорее во время выполнения. Даже d , который мы сгенерировали, добавив символ 'd' , теперь указывает на тот же объект, на который указывают a и b .

Как Интернируются Строки

В Python есть два способа создания строк на основе взаимодействия программиста:

  • Неявное интернирование
  • Явное интернирование

Неявное интернирование

Python автоматически интернирует некоторые строки в момент их создания. Интернируется ли строка или нет, зависит от нескольких факторов:

  • Все пустые строки и строки длины 1 являются интернетом.

  • Вплоть до версии 3.7 Python использовал оптимизацию peephole, и все строки длиной более 20 символов не интернировались. Однако теперь он использует оптимизатор AST , и (большинство) строк до 4096 символов интернируются.

  • Имена функций, классов, переменных, аргументов и т. Д.

  • Ключи словарей, используемых для хранения атрибутов модуля, класса или экземпляра, интернируются.

  • Строки доступны только во время компиляции, это означает, что они не будут интернированы, если их значение не может быть вычислено во время компиляции.

    • Эти строки будут, например, интернет:
    • Следующее выражение вычисляется в runtime таким образом, строка не является Интернетом.
  • Строки, содержащие символы, отличные от ASCII, скорее всего, не будут интернированы.

Если вы помните, мы говорили, что 'Hello World' + letter_d был вычислен во время выполнения, и поэтому он не будет интернирован. Поскольку не существует последовательного стандарта интернирования строк, хорошим эмпирическим правилом является идея времени компиляции/времени выполнения, где вы можете предположить, что строка будет интернирована, если она может быть вычислена во время компиляции.

Явное Интернирование

Мы часто сталкиваемся со строками, которые не лежат в условиях неявного интернирования в Python, но есть способ интернировать любую строку, которую вы хотите. В модуле sys есть функция под названием intern(immutable_object) , эта функция говорит Python хранить immutable_object (string в нашем случае) в таблице интернированной памяти.

Вы можете интернировать любой вид строки следующим образом:

import sys
c = sys.intern('Hello World'+'!')

Мы видим, что это будет работать в нашем предыдущем примере:

import sys

letter_d = 'd'

a = sys.intern('Hello World')
b = sys.intern('Hello Worl' + letter_d)

print(f"The ID of a: {id(a)}")
print(f"The ID of b: {id(b)}")
print(f"a is b? {a is b}")

Дал бы выход:

The ID of a: 26878464
The ID of b: 26878464
a is b? True

Теперь мы знаем, как и какие строки интернируются в Python. Остается один вопрос – почему было введено интернирование строк?

Преимущества интернирования струн

Интернирование строк имеет ряд преимуществ:

  • Сохранение памяти: Нам никогда не нужно сохранять два строковых объекта в памяти отдельно, если они одинаковы. Каждая новая переменная с тем же содержимым просто указывает на ссылку в литерале таблицы Интернета. Если по какой-то причине вы хотите иметь список, содержащий каждое слово и его появление в книге Джейн Остин Гордость и предубеждение , без явного интернирования вам понадобится 4.006.559 байт, а с явным интернированием каждого слова вам понадобится только 785.509 байт памяти.
  • Быстрые сравнения: Сравнение интернированных строк происходит намного быстрее, чем неинтернированных строк, что полезно, когда ваша программа имеет много сравнений. Это происходит потому, что для сравнения интернированных строк вам нужно только сравнить, совпадают ли их адреса памяти, а не сравнивать содержимое.
  • Быстрый поиск по словарю: Если ключи поиска интернированы, сравнение может быть выполнено путем сравнения указателей вместо сравнения строк, которое работает по тому же принципу, что и предыдущая точка.

Недостатки интернирования строк

Однако интернирование строк имеет некоторые недостатки и вещи, которые следует учитывать перед использованием:

  • Стоимость памяти: В случае, если ваша программа имеет большое количество строк с разными значениями и относительно меньше сравнений в целом, потому что сама интернированная таблица потребляет память. Это означает, что вы хотите интернировать строки, если у вас относительно мало строк и много сравнений между ними.
  • Стоимость времени: Вызов функции intern() стоит дорого, так как она должна управлять интернет-таблицей.
  • Многопоточные среды: Интернет-память (таблица) – это глобальный ресурс в многопоточной среде, синхронизация которого должна быть изменена. Эта проверка может потребоваться только при доступе к интернет-таблице, то есть при создании новой строки, но она может быть дорогостоящей.

Вывод

Используя интернирование строк, вы гарантируете, что будет создан только один объект, даже если вы определяете несколько строк с одинаковым содержимым. Однако вы должны помнить о балансе между преимуществами и недостатками интернирования строк и использовать его только тогда, когда считаете, что ваша программа может принести пользу.

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

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