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



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



например, скажем myString определено как:



>>> myString = "spamneggs"
>>> print(myString)
spamneggs


мне нужна функция (я назову ее process) что это:



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


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



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

927   7  

7 ответов:

правильная вещь, чтобы сделать, это использовать строку-код для декодирования строки.

>>> 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. Использование строковых кодеков намного безопаснее.

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

получается, что string_escape или unicode_escape решение не работает вообще -- в частности, оно не работает при наличии фактического Unicode.

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

unicode_escape принципиально предназначен для преобразования байтов в текст Юникода. Но во многих местах - например, исходный код 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.)

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

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

но это ужасно. Это ограничивает вас 256 латинскими символами-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

фактически правильный и удобный ответ для 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 С это ответ на вопрос " как я могу .декодировать ('string-escape') в Python3?". Как говорится в этом ответе, эта функция в настоящее время не документирована для python 3.

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

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

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

rspeer это правильно указывает, что unicode-escape включает в себя неявное декодирование с помощью latin-1, но не выполнить его. Если 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

ниже код должен работать для \n должен отображаться в строке.

import string

our_str = 'The String is \n, \n and \n!'
new_str = string.replace(our_str, '/\n', '/\n', 1)
print(new_str)

Если Вы доверяете источнику данных, просто пощечина кавычки вокруг него и eval() это?

>>> myString = 'spam\neggs'
>>> print eval('"' + myString.replace('"','') + '"')
spam
eggs

PS. добавлен счетчик evil-code-exec-теперь он будет лишать всех " прежде чем использовать eval-ить

Comments

    Ничего не найдено.