Обрабатывать 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
возвращать число, кортеж, словарь и т.д.
Все еще может быть сложно, если пользователь вводит кавычки без кавычек того типа, который вы собираетесь заключить в строку.