Обробляти послідовності втечі в рядку в Python


112

Іноді, коли я отримую вхід з файлу або користувача, я отримую рядок із послідовностями втечі. Я хотів би обробити послідовності евакуації так само, як Python обробляє послідовності втечі в рядкових літералах .

Наприклад, скажімо myString, визначається як:

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

Я хочу функцію (я назву її process), яка робить це:

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

Важливо, що функція може обробляти всі послідовності втілення в Python (перераховані в таблиці за посиланням вище).

Чи має Python функцію для цього?


1
hmmm, як саме ви очікуєте 'spam'+"eggs"+'''some'''+"""more"""обробляти рядок, що містить ?
Нас Банов

@Nas Banov Це хороший тест. Цей рядок не містить послідовностей запуску, тому після обробки він повинен бути абсолютно таким же. myString = "'spam'+\"eggs\"+'''some'''+\"\"\"more\"\"\"", print(bytes(myString, "utf-8").decode("unicode_escape"))здається, працює.
dln385

5
Більшість відповідей на це питання мають серйозні проблеми. Здається, немає жодного стандартного способу вшанувати послідовності втечі в Python, не порушуючи unicode. Відповідь, яку опублікував @rspeer, є тією, яку я прийняв для Грако, оскільки він досі розглядає всі відомі випадки.
Апалала

Відповіді:


138

Правильна річ - використовувати код '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. Використання струнних кодеків набагато безпечніше.


3
руки вниз, найкраще рішення! btw, за документами це повинно бути "string_escape" (з підкресленням), але чомусь приймає що-небудь у шаблоні "рятувальний рядок", "рядок @ втеча" та інше ... в основному'string\W+escape'
Нас Банов,

2
@Nas Banov У документації є невелика згадка про це :Notice that spelling alternatives that only differ in case or use a hyphen instead of an underscore are also valid aliases; therefore, e.g. 'utf-8' is a valid alias for the 'utf_8' codec.
dln385

30
Це рішення недостатньо добре, оскільки воно не обробляє випадок, у якому в початковому рядку є легітимні символи unicode. Якщо ви спробуєте: >>> print("juancarlo\\tañez".encode('utf-8').decode('unicode_escape')) Ви отримуєте: juancarlo añez
Апалала

2
Погодився з @Apalala: це недостатньо добре. Перегляньте відповідь rseeper нижче щодо повного рішення, яке працює в Python2 та 3!
Крістіан Айхінгер

2
Оскільки latin1передбачається unicode_escape, повторіть біт кодування / декодування, наприкладs.encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf8')
metaasaster

121

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. Зокрема, ми можемо переконатися, що застосуємо його лише до дійсних послідовностей втечі Python, які гарантовано є текстом ASCII.

План полягає в тому, що ми знайдемо послідовності втечі, використовуючи регулярний вираз, і будемо використовувати функцію в якості аргументу, 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

2
нам потрібно більше таких типів відповідей. Дякую.
v.oddou

Це os.sepвзагалі працює? Я намагаюся це зробити: patt = '^' + self.prefix + os.sep ; name = sub(decode_escapes(patt), '', name)і це не працює. Точка з комою є на місці нового рядка.
Pureferret

@Pureferret Я не дуже впевнений у тому, що ви запитуєте, але ви, мабуть, не повинні виконувати це на рядках, де зворотна косої риски має інше значення, наприклад, шлях до файлів Windows. (Це те, що у вас os.sepє?) Якщо у ваших іменах каталогу Windows відбудеться відмінна послідовність послідовностей, ситуація є майже непоправною.
rspeer

Послідовність евакуації не має в них втечі, але я отримую помилку "помилкової рядок втечі"
Pureferret

Це говорить мені , що ви закінчили який - то інший регулярний вираз за допомогою зворотного косою риси: stackoverflow.com/questions/4427174 / ...
rspeer

33

Насправді правильна та зручна відповідь для 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декодує послідовності виходу 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.

Це справжня відповідь (: Шкода, що він покладається на погано задокументовану функцію.
jwd

5
Це відповідь у ситуаціях, коли у вас є послідовності втечі - це \xвтечі байтів UTF-8. Але оскільки він розшифровує байти до байтів, він не може - і не може - розшифрувати будь-які втечі символів Unicode, що не належать до ASCII, наприклад, \uухилення.
rspeer

Тільки FYI, ця функція технічно не є загальнодоступною. дивіться bugs.python.org/issue30588
Hack5

8

ast.literal_evalФункція наближається, але він буде очікувати , що рядок , яка цитуватиметься першим.

Звичайно, інтерпретація Python зворотної косої риси залежить від того, як котирується рядок ( ""проти r""або u""потрійних лапок тощо), тож ви можете захопити ввести користувальницькі дані у відповідні лапки та перейти до literal_eval. Обертання його в лапки також не дасть literal_evalповернути число, кортеж, словник тощо.

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


Я бачу. Це , як видається потенційно небезпечним , як ви говорите: myString = "\"\ndoBadStuff()\n\"", print(ast.literal_eval('"' + myString + '"'))здається, намагаються коду виконання. Чим ast.literal_evalвідрізняється / безпечніше eval?
dln385

5
@ dln385: literal_evalніколи не виконує код. З документації "Це можна використовувати для безпечного оцінювання рядків, що містять вирази Python з ненадійних джерел, без необхідності розбирати значення".
Грег Х'югілл

2

Це поганий спосіб зробити це, але він спрацював для мене, коли я намагався інтерпретувати уникнулі восьмерики, передані в рядковому аргументі.

input_string = eval('b"' + sys.argv[1] + '"')

Варто зазначити, що існує різниця між eval та ast.literal_eval (eval є набагато небезпечнішим). Див. Розділ Використання python's eval () vs. ast.literal_eval ()?


0

Нижче код повинен працювати, \ n обов'язково відображатиметься в рядку.

import string

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

1
Це не працює так, як написано (передні косої риски змушують replaceнічого не робити), використовуються дико застарілі API (функції stringмодуля такого роду застаріли як у Python 2.0, замінені strметодами та повністю перейшли в Python 3), і тільки обробляє конкретний випадок заміни однієї нової лінії, а не загальну обробку.
ShadowRanger
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.