Важно: ниже описывается поведение python3.Хотя python2 имеет некоторые концептуальные сходства, открытое поведение будет другим.
В двух словах: из-за поддержки строкового объекта unicode в python3 это абстракция более высокого уровня.Это зависит от переводчика, как представить это в памяти.Таким образом, когда дело доходит до сериализации (например, записи текстового представления строки в файл), необходимо сначала явно кодировать ее в последовательность байтов, используя заданную кодировку (например, UTF-8).То же самое верно для преобразования байтов в строку, то есть декодирования.В python2 такое же поведение может быть достигнуто с использованием класса unicode
, тогда как str
является скорее синонимом к bytes
.
Хотя это и не прямой ответ на ваш вопрос, взгляните на следующие примеры:
import sys
e = ''
print(len(e)) # 0
print(sys.getsizeof(e)) # 49
a = 'hello'
print(len(a)) # 5
print(sys.getsizeof(a)) # 54
u = 'hello平仮名'
print(len(u)) # 8
print(sys.getsizeof(u)) # 90
print(len(u[1:])) # 7
print(sys.getsizeof(u[1:])) # 88
print(len(u[:-1])) # 7
print(sys.getsizeof(u[:-1])) # 88
print(len(u[:-2])) # 6
print(sys.getsizeof(u[:-2])) # 86
print(len(u[:-3])) # 5
print(sys.getsizeof(u[:-3])) # 54
print(len(u[:-4])) # 4
print(sys.getsizeof(u[:-4])) # 53
j = 'hello???'
print(len(j)) # 8
print(sys.getsizeof(j)) # 108
print(len(j[:-1])) # 7
print(sys.getsizeof(j[:-1])) # 104
print(len(j[:-2])) # 6
print(sys.getsizeof(j[:-2])) # 100
Строки являются неизменяемыми в Python, и это дает интерпретатору преимущество при выборе способа кодирования строки на этапе создания.Давайте рассмотрим числа сверху:
- Пустой строковый объект имеет служебную информацию в 49 байтов.
- Строка с символами ASCII длиной 5 имеет размер 49 + 5. Т.е. в кодировке используется 1 байт на символ.
- Строка со смешанными (ASCII + не-ASCII) символами занимает больше места в памятихотя длина по-прежнему составляет 8.
- Разница
u
и u[1:]
и в то же время разница u
и u[:-1]
равна 90 - 88 = 2 bytes
.Т.е. кодирование использует 2 байта на символ.Даже при том, что префикс строки может быть закодирован с 1 байтом за символ.Это дает нам огромное преимущество в том, что мы выполняем операцию индексации с постоянным временем для строк , но мы платим дополнительными затратами памяти. - Объем памяти строки
j
еще выше.Это просто потому, что мы не можем кодировать все символы в нем, используя 2 байта на символ, поэтому интерпретатор теперь использует 4 байта на каждый символ.
Хорошо, продолжайте проверять поведение.Мы уже знаем, что интерпретатор хранит строки в четном количестве байтов на символ, чтобы дать нам O(1)
доступ по индексу.Однако мы также знаем, что UTF-8
использует представление символов с переменной длиной.Давайте докажем это:
j = 'hello???'
b = j.encode('utf8') # b'hello\xf0\x9f\x98\x8b\xf0\x9f\x98\x8b\xf0\x9f\x98\x8b'
print(len(b)) # 17
Итак, мы можем видеть, что первые 5 символов кодируются с использованием 1 байта на символ, а остальные 3 символа кодируются с использованием (17 - 5)/3 = 4
байтов на символ.Это также объясняет, почему python использует 4 байта на символьное представление под капотом.
И наоборот, когда у нас есть последовательность байтов и decode
ее в строку, интерпретатор примет решение о внутренней строкепредставление (1, 2 или 4 байта на символ) и полностью непрозрачно для программиста.Единственное, что должно быть прозрачным, - это кодирование последовательности байтов.Мы должны сказать переводчику, как обращаться с байтами.Пока мы должны позволить ему определиться с внутренним представлением строкового объекта.