Pythonで文字列のエスケープシーケンスを処理する


112

ファイルまたはユーザーから入力を受け取ると、エスケープシーケンスを含む文字列が表示されることがあります。Pythonが文字列リテラルのエスケープシーケンスを処理するのと同じ方法でエスケープシーケンスを処理したいと思います

たとえば、次のようにmyString定義されているとします。

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

これを行う関数が必要です(これを呼び出しますprocess)。

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

関数がPythonのすべてのエスケープシーケンスを処理できることが重要です(上のリンクの表にリストされています)。

Pythonはこれを行う関数を持っていますか?


1
うーん、文字列を含む文字列がどの程度正確'spam'+"eggs"+'''some'''+"""more"""に処理されると思いますか?
Nas Banov

@Nas Banovそれは良いテストです。その文字列にはエスケープシーケンスが含まれていないため、処理後もまったく同じ文字列になります。myString = "'spam'+\"eggs\"+'''some'''+\"\"\"more\"\"\""、動作してprint(bytes(myString, "utf-8").decode("unicode_escape"))いるようです。
dln385

5
この質問に対するほとんどの回答には深刻な問題があります。ユニコードを壊さずにPythonのエスケープシーケンスを尊重する標準的な方法はないようです。@rspeerによって投稿された回答は、これまでにすべての既知のケースを処理するため、Grakoに採用したものです。
Apalala 14

回答:


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
手ダウン、最高のソリューション!ところで、ドキュメントでは「string_escape」(アンダースコア付き)にする必要がありますが、何らかの理由で「string escape」、「string @ escape」などのパターンを受け入れます...基本的に'string\W+escape'
Nas Banov

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に同意します:これは十分ではありません。Python2および3で機能する完全なソリューションについては、以下のrseeperの回答をご覧ください。
Christian Aichinger

2
以来latin1によって想定されunicode_escape、例えば、エンコード/デコードビットをやり直しs.encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf8')
metatoaster

121

unicode_escape 一般的には機能しません

string_escapeor 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

どういたしまして。(また、上記はPython 2のUnicodeErrorです。)

unicode_escapeコーデックは、その名前にもかかわらず、すべての非ASCIIバイトはラテン1(ISO-8859-1)エンコーディングであると仮定することが判明しました。したがって、次のようにする必要があります。

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

しかし、それはひどいです。これにより、Unicodeがまったく発明されていないかのように、256 Latin-1文字に制限されます。

>>> 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)

正規表現を追加して問題を解決する

(驚いたことに、現在2つの問題はありません。)

必要なのは、unicode_escapeASCIIテキストであることが確実なものにのみデコーダを適用することです。特に、ASCIIテキストであることが保証されている有効なPythonエスケープシーケンスにのみ適用できることを確認できます。

計画は、正規表現を使用してエスケープシーケンスを見つけ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 2015年

これos.sepはまったく機能しますか?私はこれをやろうとしている:patt = '^' + self.prefix + os.sep ; name = sub(decode_escapes(patt), '', name)そしてそれは働いていない。改行の代わりにセミコロンがあります。
Pureferret、2015

@Pureferret私はあなたが何を求めているのか本当にわかりませんが、Windowsのファイルパスなど、バックスラッシュの意味が異なる文字列では、これを実行しないでください。(それはあなたのos.sepものですか?)Windowsディレクトリ名にバックスラッシュ付きのエスケープシーケンスがある場合、状況はほとんど回復できません。
rspeer 2015

エスケープシーケンスにはエスケープがありませんが、「偽のエスケープ文字列」エラーが発生します
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_decodeb"\\n"-> b"\n"b"\\xce"-> などのASCIIエスケープシーケンスをデコードしb"\xce"ます。
  • codecs.escape_decode は、バイトオブジェクトのエンコーディングを気にする必要はありませんが、エスケープされたバイトのエンコーディングは、残りのオブジェクトのエンコーディングと一致する必要があります。

バックグラウンド:


これが本当の答え(です。あまりにもの悪いことは悪い文書化機能に依存している
JWD

5
これは、使用しているエスケープシーケンス\xがUTF-8バイトのエスケープである場合の答えです。ただし、バイトからバイトにデコードするため、エスケープなどの非ASCII Unicode文字のエスケープはデコードできません\u
rspeer 2017

参考までに、この関数は技術的には公開されていません。bugs.python.org/issue30588を
Hack5

8

ast.literal_eval関数が近づくが、それは文字列が適切に最初に引用されることを期待します。

もちろん、Pythonのバックスラッシュエスケープの解釈は、文字列の引用方法(""vs r""vs u""、三重引用符など)に依存するため、ユーザー入力を適切な引用符で囲み、に渡したい場合がありますliteral_eval。引用符で囲むとliteral_eval、数値、タプル、辞書などが返されなくなります。

ユーザーが文字列をラップするつもりである型の引用符で囲まれていない引用符をユーザーが入力した場合でも、物事はトリッキーになるかもしれません。


そうですか。あなたが言うように、これは潜在的に危険であるようです:myString = "\"\ndoBadStuff()\n\""print(ast.literal_eval('"' + myString + '"'))コードを実行しようとするようです。どのast.literal_evalように違う/安全ですevalか?
dln385

5
@ dln385:literal_evalコードを実行しません。ドキュメントから、「これは、自分で値を解析する必要なしに、信頼できないソースからのPython式を含む文字列を安全に評価するために使用できます。」
グレッグヒューギル

2

これは悪いやり方ですが、文字列引数で渡されたエスケープされた8進数を解釈しようとするときに私にとってはうまくいきました。

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

evalとast.literal_evalの間に違いがあることは言及する価値があります(evalの方がはるかに安全ではありません)。Pythonのeval()と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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.