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つになるとちょっと追いつかなくなりました。一つ一つ挙動を追って行けばわからないこともないですね。