LoginSignup
3
0

More than 3 years have passed since last update.

Pythonで正規表現を使って\を含む文字列に置換したいときの挙動の調査

Posted at

Pythonの正規表現モジュールrere.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と表示されます。挙動としては次のようになります。

  1. re.subで最初の\が2番めの\のエスケープに使われる、結果的に\aが残る
  2. \aが生成された瞬間、Pythonは\aを制御コードとして解釈し、変換し\x07となる。
  3. 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と表示されます

  1. re.subで最初の\が2番めの\のエスケープに使われ、3番めの\が4番めの\のエスケープに使われ、結果的に\\aが残る
  2. print\\aの最初の\が2番めの\のエスケープに使われ、\aと表示される

6つの場合

new_str = re.sub("bbb","\\\\\\a",old_str)
print(new_str)

だと\□_cccと表示されます。

  1. re.subで最初の\が2番めの\のエスケープに使われ、3番めの\が4番めの\のエスケープに使われ…と3回エスケープが起こり結果的に\\\aが残る
  2. \\\aが生成された瞬間、Pythonは最初の\を2番めの\のエスケープとして解釈しそのままにし、残った\aを制御コードとして解釈し、\x07と変換する。結果的に\\\x07となる。
  3. 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と表示されます。

  1. re.subで最初の\が2番めの\のエスケープに使われ、3番めの\が4番めの\のエスケープに使われ…と4回エスケープが起こり結果的に\\\\aが残る
  2. 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つになるとちょっと追いつかなくなりました。一つ一つ挙動を追って行けばわからないこともないですね。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0