Преобразование последовательности Unicode в строку в Python3, но разрешить пути в строке

1

Существует хотя бы один связанный с этим вопрос о SO, который оказался полезным при попытке декодирования последовательностей Unicode.

Я препровождаю много текстов с множеством разных жанров. Некоторые из них экономичны, некоторые технические, и так далее. Одним из предостережений является преобразование последовательностей Unicode:

'Korado output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\u0115ch \u010camek.

Такая строка должна быть преобразована в фактические символы:

'Korado output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojtĕch Čamek.

что можно сделать следующим образом:

s = "'Korado output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\u0115ch \u010camek."
s = s.encode('utf-8').decode('unicode-escape')

(По крайней мере, это работает, когда s является строкой ввода, взятой из текстового файла с кодировкой utf-8. Я не могу заставить это работать на онлайн-службу, такую как REPL.it, где вывод кодируется/декодируется по-разному.)

В большинстве случаев это прекрасно работает. Однако, когда пути структуры каталога видны во входной строке (часто это касается технических документов в моем наборе данных), тогда происходит UnicodeDecodeError.

Учитывая следующие данные unicode.txt:

'Korado output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\u0115ch \u010camek, Financial Director and Director of Controlling.
Voor alle bestanden kan de naam met de volledige padnaam (bijvoorbeeld: /u/slick/udfs/math.a (op UNIX), d:\udfs\math.dll (op Windows)).

С помощью подробного представления:

b"'Korado output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\\u0115ch \\u010camek, Financial Director and Director of Controlling.\r\nVoor alle bestanden kan de naam met de volledige padnaam (bijvoorbeeld: /u/slick/udfs/math.a (op UNIX), d:\\udfs\\math.dll (op Windows))."

Следующий сценарий завершится неудачей при декодировании второй строки во входном файле:

with open('unicode.txt', 'r', encoding='utf-8') as fin, open('unicode-out.txt', 'w', encoding='utf-8') as fout:
    lines = ''.join(fin.readlines())
    lines = lines.encode('utf-8').decode('unicode-escape')

    fout.write(lines)

Со следом:

Traceback (most recent call last):
  File "C:/Python/files/fast_aligning/unicode-encoding.py", line 3, in <module>
    lines = lines.encode('utf-8').decode('unicode-escape')
UnicodeDecodeError: 'unicodeescape' codec can't decode bytes in position 275-278: truncated \uXXXX escape

Process finished with exit code 1

Как я могу гарантировать, что первое предложение по-прежнему "переведено" правильно, как показано выше, но второе остается нетронутым? Ожидаемый результат для двух указанных строк будет таким образом, чтобы следующая строка изменилась, а вторая - нет.

'Korado output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojtĕch Čamek.
Voor alle bestanden kan de naam met de volledige padnaam (bijvoorbeeld: /u/slick/udfs/math.a (op UNIX), d:\udfs\math.dll (op Windows)).
  • 0
    Так что для ясности, сам файл unicode.txt самом деле содержит строки, которые имеют escape-последовательности \uXXXX , и это не только как Python печатает это?
  • 0
    @AKX Это верно.
Показать ещё 17 комментариев
Теги:
python-3.x
unicode

3 ответа

1
Лучший ответ

Ввод неоднозначен. Правильного ответа не существует в общем случае. Мы могли бы использовать эвристику, которая выводит результат, который выглядит почти всегда, например, мы могли бы использовать правило, такое как "если последовательность \uxxxx (6 символов) является частью существующего пути, а затем не интерпретирует ее как escape Unicode "и то же самое для \Uxxxxxxxx (10 символов), например, вход, который аналогичен b"c:\\U0001f60f\\math.dll" с вопросом: b"c:\\U0001f60f\\math.dll" может быть интерпретирован по-разному в зависимости от того, c:\U0001f60f\math.dll самом деле существует на диске:

#!/usr/bin/env python3
import re
from pathlib import Path


def decode_unicode_escape_if_path_doesnt_exist(m):
    path = m.group(0)
    return path if Path(path).exists() else replace_unicode_escapes(path)


def replace_unicode_escapes(text):
    return re.sub(
        fr"{unicode_escape}+",
        lambda m: m.group(0).encode("latin-1").decode("raw-unicode-escape"),
        text,
    )


input_text = Path('broken.txt').read_text(encoding='ascii')
hex = "[0-9a-fA-F]"
unicode_escape = fr"(?:\\u{hex}{{4}}|\\U{hex}{{8}})"
drive_letter = "[a-zA-Z]"
print(
    re.sub(
        fr"{drive_letter}:\S*{unicode_escape}\S*",
        decode_unicode_escape_if_path_doesnt_exist,
        input_text,
    )
)

Укажите фактическое кодирование файла broken.txt в read_text() если в кодированном тексте есть символы, отличные от ascii.

Какое конкретное регулярное выражение используется для извлечения путей зависит от типа ввода, который вы получаете.

Вы можете усложнить код, пытаясь заменить одну возможную последовательность Unicode за раз (количество замещений растет экспоненциально с количеством кандидатов в этом случае, например, если в пути есть 10 возможных escape-последовательностей Unicode, тогда есть 2**10 декодированных путей, чтобы попробовать).

1

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

input = br"""
'Korado output has gone up from 180,000 radiators per year to almost 1.7 million today,' says Vojt\u0115ch \u010camek, Financial Director and Director of Controlling.
Voor alle bestanden kan de naam met de volledige padnaam (bijvoorbeeld: /u/slick/udfs/math.a (op UNIX), d:\udfs\math.dll (op Windows)).
"""

print(input.decode('raw_unicode_escape', 'ignore'))

выходы

"Производство Korado выросло с 180 000 радиаторов в год до почти 1,7 миллиона сегодня", - говорит Войтчуч Чамек, финансовый директор и директор по контролю.
В дополнение к лучшему кан-наам встретил volledige padnaam (bijvoorbeeld: /u/slick/udfs/math.a(op UNIX), d: s\math.dll (операционная система Windows)).

Обратите внимание, что \udf в d:\udfs, так как кодек пытается начать чтение последовательности \uXXXX, но \uXXXX в s.

Альтернативным (скорее всего, медленным) было бы использование регулярного выражения для поиска действительных последовательностей Unicode в декодированных данных. Это предполагает, что .decode() полную входную строку, поскольку UTF-8 возможен. (.encode().decode() необходим, поскольку строки не могут быть закодированы, просто байты. Можно также использовать chr(int(m.group(0)[2:], 16)).)

escape_re = re.compile(r'\\u[0-9a-f]{4}')
output = escape_re.sub(lambda m: m.group(0).encode().decode('unicode_escape'), input.decode()))

выходы

"Производство Korado выросло с 180 000 радиаторов в год до почти 1,7 миллиона сегодня", - говорит Войтчуч Чамек, финансовый директор и директор по контролю.
В дополнение к лучшему кан-наам встретил volledige padnaam (bijvoorbeeld: /u/slick/udfs/math.a(op UNIX), d:\udfs\math.dll (для Windows)).

Так как \udf не имеет 4 шестнадцатеричных символов, путь d:\udfs здесь.

  • 1
    Это прекрасно работает (и относительно быстро) - но у него есть одна большая проблема, а именно, что специальные символы будут генерировать синтаксические ошибки. Например, добавьте België (нидерландский для Belgium ) к этому input и он не будет работать. Поэтому я думаю, что ваша вторая идея - это путь. Я возьму медленную скорость с этим.
  • 1
    Чтобы быть точным, b"België" является синтаксической ошибкой, потому что байтовые литералы могут содержать только символы ASCII. Но в любом случае, первое решение не декодирует UTF-8, поэтому оно не применимо, если строка содержит такие символы.
0

Я уже написал этот код, когда AKX отправил свой ответ. Я все еще думаю, что это применимо.

Идея состоит в том, чтобы захватить юникод-кандидатов с помощью регулярных выражений (и попытаться исключить пути, например, части, которым предшествует любая буква и двоеточие (например, c:\udfff). Если декодирование не выполняется, мы вернем исходную строку.

with open('unicode.txt', 'r', encoding='utf-8') as fin, open('unicode-out.txt', 'w', encoding='utf-8') as fout:
    lines = ''.join(fin.readlines())
    lines = lines.strip()
    lines = unicode_replace(lines)
    fout.write(lines)


def unicode_replace(s):
    # Directory paths in a text are seen as unicode sequences but will fail to decode, e.g. d:\udfs\math.dll
    # In case of such failure, we'll pass on these sentences - we don't try to decode them but leave them
    # as-is. Note that this may leave some unicode sequences alive in your text.
    def repl(match):
        match = match.group()
        try:
            return match.encode('utf-8').decode('unicode-escape')
        except UnicodeDecodeError:
            return match

    return re.sub(r'(?<!\b[a-zA-Z]:)(\\u[0-9A-Fa-f]{4})', repl, s)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню