Вопрос-Ответ

Process escape sequences in a string in Python

Обрабатывать escape-последовательности в строке в Python

Иногда, когда я получаю входные данные от файла или пользователя, я получаю строку с escape-последовательностями в ней. Я хотел бы обрабатывать escape-последовательности таким же образом, как Python обрабатывает escape-последовательности в строковых литералах.

Например, допустим, myString определяется как:

>>> myString = "spam\\neggs"
>>> print(myString)
spam\neggs

Мне нужна функция (я назову ее process), которая делает это:

>>> print(process(myString))
spam
eggs

Важно, чтобы функция могла обрабатывать все escape-последовательности в Python (перечислены в таблице по ссылке выше).

Есть ли в Python функция для этого?

Переведено автоматически
Ответ 1

Правильнее всего использовать код 'string-escape' для декодирования строки.

>>> myString = "spam\\neggs"
>>> decoded_string = bytes(myString, "utf-8").decode("unicode_escape") # python3
>>> decoded_string = myString.decode('string_escape') # python2
>>> print(decoded_string)
spam
eggs

Не используйте AST или eval. Использование строковых кодеков намного безопаснее.

Ответ 2

unicode_escape вообще не работает

Оказывается, что решение string_escape or unicode_escape не работает в целом - в частности, оно не работает при наличии фактического Unicode.

Если вы можете быть уверены, что каждый символ, отличный от ASCII, будет экранирован (и помните, что все, что находится за пределами первых 128 символов, не является ASCII), unicode_escape будет сделано правильно для вас. Но если в вашей строке уже есть какие-либо буквенные символы, отличные от ASCII, все пойдет не так.

unicode_escape в основе своей предназначен для преобразования байтов в текст в формате Unicode. Но во многих местах - например, в исходном коде Python - исходными данными уже является текст в формате Unicode.

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

Следующие примеры приведены в Python 3, так что строковые литералы более чистые, но та же проблема существует с немного разными проявлениями как в Python 2, так и в 3.

>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve test

Ну, это неправильно.

Новый рекомендуемый способ использования кодеков, которые декодируют текст в текст, - это прямой вызов codecs.decode. Помогает ли это?

>>> import codecs
>>> print(codecs.decode(s, 'unicode_escape'))
naïve test

Вовсе нет. (Кроме того, приведенное выше является ошибкой UnicodeError в Python 2.)

В unicode_escape кодеке, несмотря на его название, предполагается, что все байты, отличные от ASCII, находятся в кодировке Latin-1 (ISO-8859-1). Итак, вам придется сделать это следующим образом:

>>> print(s.encode('latin-1').decode('unicode_escape'))
naïve test

Но это ужасно. Это ограничивает вас 256 символами Latin-1, как будто Unicode вообще никогда не изобретали!

>>> print('Ernő \\t Rubik'.encode('latin-1').decode('unicode_escape'))
UnicodeEncodeError: 'latin-1' codec can't encode character '\u0151'
in position 3: ordinal not in range(256)

Добавление регулярного выражения для решения проблемы

(Удивительно, но теперь у нас действительно есть две проблемы.)

Что нам нужно сделать, так это применить unicode_escape декодер только к тому, что мы уверены, что это текст ASCII. В частности, мы можем убедиться, что применяем это только к допустимым escape-последовательностям Python, которые гарантированно будут текстом ASCII.

План таков: мы найдем escape-последовательности, используя регулярное выражение, и используем функцию в качестве аргумента для re.sub, чтобы заменить их неэкранированным значением.

import re
import codecs

ESCAPE_SEQUENCE_RE = re.compile(r'''
( \\U........ # 8-digit hex escapes
| \\u.... # 4-digit hex escapes
| \\x.. # 2-digit hex escapes
| \\[0-7]{1,3} # Octal escapes
| \\N\{[^}]+\} # Unicode characters by name
| \\[\\'"abfnrtv] # Single-character escapes
)'''
, re.UNICODE | re.VERBOSE)

def decode_escapes(s):
def decode_match(match):
return codecs.decode(match.group(0), 'unicode-escape')

return ESCAPE_SEQUENCE_RE.sub(decode_match, s)

И с этим:

>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő Rubik
Ответ 3

Действительно правильный и удобный ответ для python 3:

>>> import codecs
>>> myString = "spam\\neggs"
>>> print(codecs.escape_decode(bytes(myString, "utf-8"))[0].decode("utf-8"))
spam
eggs
>>> myString = "naïve \\t test"
>>> print(codecs.escape_decode(bytes(myString, "utf-8"))[0].decode("utf-8"))
naïve test

Подробнее о codecs.escape_decode:


  • codecs.escape_decode это байтовый декодер

  • codecs.escape_decode декодирует escape-последовательности ascii, такие как: b"\\n" -> b"\n", b"\\xce" -> b"\xce".

  • codecs.escape_decode не заботится и не нуждается в знании о кодировке объекта byte, но кодировка экранированных байтов должна соответствовать кодировке остальной части объекта.

Справочная информация:


  • @rspeer правильно: unicode_escape это неправильное решение для python3. Это происходит потому, что unicode_escape декодирует экранированные байты, затем декодирует байты в строку Unicode, но не получает информации о том, какой кодек использовать для второй операции.

  • @Jerub правильно: избегайте AST или eval.

  • Я впервые обнаружил codecs.escape_decode из этого ответа на вопрос "как мне .decode ('string-escape') в Python3?". Как указано в этом ответе, эта функция в настоящее время не документирована для python 3.

Ответ 4

Функция ast.literal_eval подходит близко, но сначала она ожидает, что строка будет заключена в правильные кавычки.

Конечно, интерпретация экранирования обратной косой черты в Python зависит от того, как строка заключена в кавычки ("" vs r"" против u"", тройные кавычки и т.д.), Поэтому вы можете захотеть заключить пользовательский ввод в подходящие кавычки и передать в literal_eval. Заключать ее в кавычки также не позволит literal_eval возвращать число, кортеж, словарь и т.д.

Все еще может быть сложно, если пользователь вводит кавычки без кавычек того типа, который вы собираетесь заключить в строку.

python string