Управляющие последовательности процесса в строке в Python
Иногда, когда я получаю ввод от файла или пользователя, я получаю строку с escape-последовательностями в ней. Я хотел бы обработать escape-последовательности таким же образом, что Python обрабатывает escape-последовательности в строковых литералах.
Например, пусть myString
определяется как:
>>> myString = "spam\\neggs"
>>> print(myString)
spam\neggs
Мне нужна функция (я буду называть ее process
), которая делает это:
>>> print(process(myString))
spam
eggs
Важно, чтобы функция могла обрабатывать все escape-последовательности в Python (перечисленные в таблице в ссылке выше).
Есть ли у Python функция для этого?
Ответы
Ответ 1
Правильная вещь - использовать код "escape-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
или 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
не интересуется или не нуждается в кодировании байтового объекта, но кодировка экранированных байтов должна соответствовать кодировке остальной части объекта.
Фон:
- @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""
vs u""
, тройные кавычки и т.д.), Поэтому вам может понадобиться обернуть ввод пользователя в подходящие кавычки и перейти к literal_eval
. Объединение его в кавычки также не позволит literal_eval
возвращать число, кортеж, словарь и т.д.
Все может оказаться сложным, если пользователь вводит кавычки без кавычек типа, который вы собираетесь обернуть вокруг строки.
Ответ 5
Ответ rspeer правильно указывает, что unicode-escape
подразумевает неявное декодирование с использованием latin-1
, но на этом не происходит. Если unicode-escape
корректно декодирует unicode-escape
файлы, но неправильно обрабатывает необработанные байты без ASCII, расшифровывая их как latin-1
, то прямое исправление не должно принимать регулярное выражение, а затем перекодировать их как latin-1
после (для отмены ошибочная часть процесса), а затем декодировать в правильной кодировке. Например, пример неправильного использования:
>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve test
можно сделать тривиально правильным, добавив .encode('latin-1').decode('utf-8')
, делая это:
>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape').encode('latin-1').decode('utf-8'))
naïve test
# Or using codecs.decode to replace the first encode/decode pair with a single text->text transform:
>>> print(codecs.decode(s, 'unicode_escape').encode('latin-1').decode('utf-8'))
naïve test
Конечно, это много назад и вперед, и я бы не захотел встраивать его в свой код, но его можно разделить на автономную функцию, которая работает как для str
и для bytes
(с необязательным шагом декодирования для bytes
если результат находится в известной кодировке):
def decode_escapes(s, encoding=None):
if isinstance(s, str):
if encoding is not None:
return TypeError("Do not pass encoding for string arguments")
# UTF-8 will allow correct interpretation of escapes when bytes form
# interpreted as latin-1
s = s.encode('utf-8')
encoding = 'utf-8'
decoded = s.decode('unicode_escape').encode('latin-1')
if encoding is not None:
# If encoding is provided, or we started with an arbitrary string, decode
decoded = decode.decode(encoding)
return decoded
Ответ 6
Ниже приведен код, который должен работать для \n, который должен отображаться в строке.
import string
our_str = 'The String is \\n, \\n and \\n!'
new_str = string.replace(our_str, '/\\n', '/\n', 1)
print(new_str)
Ответ 7
Если вы доверяете источнику данных, просто удаляйте кавычки вокруг него и eval() it?
>>> myString = 'spam\\neggs'
>>> print eval('"' + myString.replace('"','') + '"')
spam
eggs
PS. добавлена противопожарная мера зла-кода-exec - теперь она будет разбивать все "
до eval-ing