Существует ли питонический способ усечения строки Unicode на максимальное количество байтов?

#python #python-3.x #unicode

#python #python-3.x #юникод

Вопрос:

Если API принимает некоторое строковое значение с ограничением на количество байтов, но принимает Unicode, есть ли лучший способ сократить строку с допустимым Unicode?

 def truncate(string: str, length: int):
    """Shorten an Unicode string to a certain length of bytes."""
    if len(string.encode()) <= length:
        return string

    chars = list(string)
    while sum(len(char.encode()) for char in chars) > length:
        chars.pop(-1)

    return "".join(chars)
 

Комментарии:

1. Непонятно, о чем вы спрашиваете. Вы используете мировой «байт», но в Unicode есть символы (или кодовые точки). Вы хотите иметь фиксированную длину в символах? Или вам нужна фиксированная длина в байтах (но это зависит от кодировки, вы имеете в виду UTF-8?) В этом случае решение может быть сложным. Является ли длина длинной?

2. То, что вы просите, не очень «юникодоническое» :-). То есть он не учитывает сложности сокращения коротких строк на разных языках, которые поддерживает Unicode. Прочитайте о «Символах и символах» в Unicode TR # 17 Модель кодирования символов Unicode . Простое усечение может привести к получению плохо сформированных последовательностей байтов или к повреждению глифов из-за пропущенных символов. Если вы укажете желаемое поведение глифа, мы сможем дать вам лучшие ответы о Pythonic способах его получения.

3. В данном конкретном случае мне все равно, Sendgrid лжет о принятии 50 символов для имени / фамилии, когда это 50 байт. В БД у нас есть до 128 символов, так что мы должны где-то их сократить. Тем не менее, если есть более чистое решение, пожалуйста, давайте послушаем его!

Ответ №1:

Это должно работать в Python-3:

 bytes_ = string.encode()
try:
    return bytes_[:length].decode()
except UnicodeDecodeError as err:
    return bytes_[:err.start].decode()
 

В основном мы усекаем при первой ошибке декодирования.UTF-8 — это префиксный код. Поэтому декодер всегда должен иметь возможность видеть, когда строка усекается в середине символа. Странности могут возникать с акцентами и прочим. Я еще не продумал этот вопрос до конца. Возможно, нам тоже нужна некоторая нормализация.

В Python-2 обязательно укажите кодировку.

Комментарии:

1. Вероятно, это должно явно указывать кодировку. По умолчанию используется UTF-8 на большинстве нормальных платформ, но есть много пользователей, которые используют Windows, потому что они не знают ничего лучшего или потому, что должны.

2. @tripleee В Python 3, .encode() и .decode() по умолчанию имеют значение ‘utf8’ и в Windows.

3. К сожалению, символы / кодовые точки также не соответствуют графическим единицам. В большинстве случаев это будет работать нормально, но может привести к разрыву объединяющего символа из его базового блока или разделению последовательности смайликов / флагов или аналогичного. Это может быть приемлемым ограничением. Использование нормализации поможет удалить некоторые комбинирующие символы, если они доступны.

4. Я поддерживаю это, потому что это довольно Pythonic способ обрезать строку текста Unicode, закодированную как UTF-8, с числом, меньшим или равным ограничению в байтах. Однако этот вид произвольного отсечения не очень «уникод-оничный» 🙂 . См. Комментарий о модели символов и символов под основным вопросом.