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

Струны Python не UTF-8

За последние десять лет UTF-8 стал ведущим представлением данных Unicode. Практически все мо … Теги с Python, Unicode.

За последние десять лет UTF-8 стал ведущим представлением данных Unicode. Практически каждый современный веб-сайт обслуживается как UTF-8 и большинство современных языков программирования Tout первоклассной поддержки для него.

Для тех, кто не знает, о чем UTF-8, это вариабельная ширина двоичного кодирования формата для точек кода Unicode. Многие Другое Написания были сделаны на этом, что вы должны абсолютно проконсультироваться для более подробной информации. Прямо сейчас, мой фокус – это аспект переменной ширины, который очень полезен, но имеет два раздражающих последствия:

  1. Кодовые точки не могут быть случайно проиндексированы в постоянном времени.

  2. Количество точек кода в строке не может быть определено в постоянном времени.

Если вы работали с Rust или Go, эти ограничения знакомы. Эти языки делают работу с UTF-8 гораздо менее подробно и подвержены ошибкам, чем говорят, C, но они не скрывают эти два раздражения. Вместо этого они позволяют вам либо работать непосредственно с отдельными байтами, либо использовать синтаксис, который явно передается через точки кода строки, чтобы подсчитать или индексировать их.

Но как насчет Python? В Python мы регулярно делаем такие вещи, как:

>>> s = 'foobar'
>>> s[3]
'b'
>>> len(s)
6

Правило, что Python может реализовывать как эти операции как линейную итерацию по точкам кода строки. Python скрывает такие вещи под капотом все время. Мы можем легко проверить это некрасивочно, сделав ориентир, сколько времени требуется, чтобы индекс в разные положения большой строки:

>>> timeit.timeit('assert s[3] == "b"', 's = "foobar" * 100_000', number=1_000_000)
0.04597386400200776
>>> timeit.timeit('assert s[500001] == "b"', 's = "foobar" * 100_000', number=1_000_000)
0.04409515299994382

Если Python итерация по кодам точкам, чтобы выполнить индексацию строки, мы увидели заметную разницу во время выполнения этих фрагментов, но мы не можем. Так что же происходит? Краткий ответ – это не UTF-8.

Поскольку Python 3.0 UTF-8 был предполагаемым кодированием исходного кода, и если вы строим строку из A Байты , кодировка по умолчанию – UTF-8. Так что вы можете быть прощены за предположить, как я сделал, этот python’s ул Тип – UTF-8.

Так что это на самом деле? Я узнал, глядя через Исходный код Для внедрения Unicode Python, но он также документирован в PEP 393 Отказ По сути, под капотом современные версии Python сохраняют вкладки на содержание любого ул ...| объект. Если каждый символ в нем находится в действительном диапазоне латин-1 (8 битов), он использует, что кодирование для представления в памяти, если каждый символ находится в пределах допустимого диапазона кодовой точки UTF-16, он использует UTF -16, Иначе он использует UTF-32.

Эта фиксированная ширина кодировки неплохая. Если вы в основном работаете с однородным европейским текстом, это так же, как космически, так как UTF-8, но это дает вам постоянную промежуточную индексацию и вычисление длины. Существует два компромиссов, конечно:

  1. Текст с даже одним символом за пределами диапазона латин-1 мгновенно удваивается в использовании памяти, и снова удваивается с одним символом за пределами UTF-16.

  2. Изменение строки может потенциально требовать перераспределения всей строки к новой ширине точки кода.

Строки Python неизменяются, поэтому я уверен, что Devs почувствовал, что вторая проблема не важна, так как кто-либо пытается сделать что-то вдоль этих строк, уже подписал себя на распределение памяти. На самом деле, хотя я думаю, что большинство пользователей Python неявно рассмотрит MyString + 'Foo' быть недорогой операцией. Это приводит к нашему следующему эталону:

>>> timeit.timeit('s + "foobar"', 's = "foobar" * 100_000', number=100_000)
2.1425487420019635
>>> timeit.timeit('s + "🏴"', 's = "foobar" * 100_000', number=100_000)
10.080080990999704

Начиная с 600 000 символов латин-1 строки, требуется 5x дольше для объединения шотландского флага эмодзи, чем для объединения еще 6 символов латин-1. Чтобы быть справедливым, шотландский флаг Emoji весит в колоссальном 7 кодовых точках, но это тот факт, что они 32-битные кодовые точки, которые вызывают это, не то, что есть 7 из них.

>>> sys.getsizeof("foobar" * 100_000)
600049
>>> sys.getsizeof("foobar" * 100_000 + "🏴")
2400104

Добавление того, что один эмодзи в кварталах размером объекта в памяти.

Каждый серьезный программист Python хорошо знает, что эти виды ловушек производительности существуют, особенно когда вы делаете что-то, что язык SPEC не воспринимал в качестве общего случая использования. Но персонажи, требующие 1 или более 32-битных точек Unicode кода Unicode, чтобы представлять их все более распространенные, а поддержка языка программирования не так велика, как я думал, это будет.

Не верь мне? Затем я бросаю вызов вам написать мне функцию Python, которая распознает палиндромы. Это в основном класс школьных вещей, верно? Вот мои тестовые случаи:

>>> is_palindrome("🏴🤷🏾‍♂️🏴")
True
>>> is_palindrome("🇬🇧🇧🇬")
False
>>> is_palindrome("👩‍🍼🍼‍👩")
False

Оставьте комментарий, если вы думаете, что у вас есть нефте решение.

Там есть много плохого и дезинформации, окружающего Unicode в Интернете, поэтому, если есть интерес, я могу написать несколько постов, как это разъясняет некоторые подводные камни.

Оригинал: “https://dev.to/bplevin36/python-strings-are-not-utf-8-2dfj”