Pythonの正規表現モジュールreのre.sub()を使って、検索した文字列を\(エスケープ、バックスラッシュ、円マーク)を含む文字列に置換したいとき、予期しない挙動やエラーを起こすことがあります。その備忘録を記します。
通常の文字列操作、例えば+を使って文字列を連結する際などは、文字列内の\は別の\でエスケープさえしておけば、特に予期しない挙動は起こしません。
test_str="\\a"+"_ccc"
print(test_str)# \a_ccc
しかし、re.sub()はre.sub(検索したい文字列,置換後の文字列,元の文字列)と使いますが、この「置換後の文字列」内の\がre.subを実行した時点でエスケープとして解釈されてしまいます。これについて挙動を詳しく調べてみます。
挙動の調査
old_str = "bbb_ccc"
このbbbを\を含む文字列に置換することに考えます。
new_str = re.sub("bbb","\\a",old_str)
としたときの\\aの\を2つ、4つ、6つ、8つとしたときの挙動を見てみます。また、最後にraw文字列を用いた場合についても調べます。
2つの場合
new_str = re.sub("bbb","\\a",old_str)
print(new_str)
で□_cccと表示されます。挙動としては次のようになります。
-
re.subで最初の\が2番めの\のエスケープに使われる、結果的に\aが残る -
\aが生成された瞬間、Pythonは\aを制御コードとして解釈し、変換し\x07となる。 -
printで\x07が□と表示される
この際、変換後の文字列を\\aではなく\\cとする、つまり
new_str = re.sub("bbb","\\c",old_str)
とすると、は\cに対応する制御コードがないので2の段階、つまりre.subを実行した段階でbad escapeエラーとなります。
制御コードとは\nが改行コードに該当するなど、文字ではなく動作を指定するものです。\aはビープ音の制御コードに該当するらしいです。
制御文字(せいぎょもじ、英: control character)とは、文字コードの規格で定義される文字のうち、ディスプレイ・プリンター・通信装置などに対して、特別な動作(制御)をさせるために使う文字である。
制御文字 (フリー百科事典『ウィキペディア(Wikipedia)』 2020年12月6日 (日) 23:14 UTC)
4つの場合
new_str = re.sub("bbb","\\\\a",old_str)
print(new_str)
だと\a_cccと表示されます
-
re.subで最初の\が2番めの\のエスケープに使われ、3番めの\が4番めの\のエスケープに使われ、結果的に\\aが残る -
printで\\aの最初の\が2番めの\のエスケープに使われ、\aと表示される
6つの場合
new_str = re.sub("bbb","\\\\\\a",old_str)
print(new_str)
だと\□_cccと表示されます。
-
re.subで最初の\が2番めの\のエスケープに使われ、3番めの\が4番めの\のエスケープに使われ…と3回エスケープが起こり結果的に\\\aが残る -
\\\aが生成された瞬間、Pythonは最初の\を2番めの\のエスケープとして解釈しそのままにし、残った\aを制御コードとして解釈し、\x07と変換する。結果的に\\\x07となる。 -
printで\\\x07の最初の\が2番めの\のエスケープに使われ、\x07が□と表示され、結果的に\□と表示される
この際も、変換後の文字列を\\\\\\aではなく\\\\\\cとすると\cに対応する制御コードがないのでre.subを実行した段階でbad escapeエラーが発生します。
8つの場合
new_str = re.sub("bbb","\\\\\\\\a",old_str)
print(new_str)
だと\\a_cccと表示されます。
-
re.subで最初の\が2番めの\のエスケープに使われ、3番めの\が4番めの\のエスケープに使われ…と4回エスケープが起こり結果的に\\\\aが残る -
printで\\\\aの最初の\が2番めの\のエスケープに使われ…と2回エスケープが起こり、\\aと表示される
raw文字列
raw文字列さえ使っておけばエラーが起こらないと思うかもしれません。raw文字列は"..."や'...'の前にrをつけることで文字列内の\をエスケープできるものです。たとえば
test_str=r"\a"
は
test_str="\\a"
と同じことです。つまり単純に文字列内の\が二倍になると考えればいいわけです。よって\が3つの"\\\a"の前にrをつけたr"\\\a"は、rをつけない場合の\が6つの場合と同じです。つまり
new_str = re.sub("bbb",r"\\\a",old_str)
print(new_str)
は\□_cccと表示されます。当然、変換後の文字列をr"\\\c"とすれば先程述べたのと同じ理由でbad escapeエラーが発生します。raw文字列さえ使っておけばエラーが起こらない、ということではないわけですね。
ちなみに文字列の変数をraw文字列に変換したい場合は組み込み関数のrepr()を使えばいいです。
test_str="\\a"
raw_str=repr(test_str)
このときraw_strには、'\\\\a'と先頭と末尾に'が付け加わったraw文字列が保存されているので、スライスを使って
raw_str[1:-1]
とすればraw文字列が取り出せます。
感想
2つの場合、4つの場合の挙動はなんとなく解っていたのですが、6つと8つになるとちょっと追いつかなくなりました。一つ一つ挙動を追って行けばわからないこともないですね。