Автор оригинала: 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
просто указывает на ранее выделенную память.
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 ()
, чтобы чувствовать себя с ней комфортно. Эта концепция может помочь вам улучшить дизайн и производительность вашего кода. Это может помочь вам и на следующем собеседовании.