Python の raw 文字列を用いて正規表現を書く

任意の文字列に \' をマッチさせようとして、混乱したので書きます。

例題

  • pattern: \'
  • string: backslash \' single quote

以下、raw 文字列を使った場合と使わない場合で、それぞれ書き方の答え

pattern も string も raw 文字列
>>> re.findall(r'\\\'', r'backslash \' single quote')
["\\'"]
>>> print re.findall(r'\\\'', r'backslash \' single quote')[0]
\'
pattern も string も 普通の文字列(バックスラッシュのエスケープが必要)
>>> re.findall('\\\\\'', r'backslash \\\' single quote')
["\\'"]
>>> print re.findall('\\\\\'', r'backslash \\\' single quote')[0]
\'

エスケープ

混乱のもとは、「正規表現エスケープ処理」と「Python 文字列のエスケープ処理」を二重に考える必要があるからでした。
raw 文字列で書くと、「Python 文字列としてのエスケープ処理」は必要なくなりますが、「正規表現としてのエスケープ処理」は必要です。

ちなみに、それぞれのエスケープルールは以下のようなもの。

Python 文字列における主なエスケープシーケンス

(参考: Python リファレンスマニュアル

  • \\ : backslash -> raw 文字列だとバックスラッシュ1つの \ でよい。
  • \' : single quotation (single quotation 内) -> raw 文字列で書いても必要だが、raw 文字列だと \ が残り、 ' 単体では書けない
  • \" : double quotation (double quotation 内) -> raw 文字列で書いても必要だが、raw 文字列だと \ が残り、 " 単体では書けない
Python 正規表現 における主なエスケープシーケンス

(参考: Python ライブラリリファレンス

etc.

注意すべきは、以下の点です。

  • バックスラッシュが両方のエスケープ対象になっている、ということ
  • ' や " は、一見エスケープ対象っぽいけど、正規表現では実はエスケープ対象ではないこと
  • raw 文字列であっても、"" の中では " をエスケープしなくてはならないが、エスケープ時の \ が出力されること
  • 正規表現で、意味のない \ は無視されること
>>> print re.findall('\\あ', 'あ')
['\xe3\x81\x82']
>>> print re.findall('\\d', 'd')
[]
>>> print re.findall('\\y', 'y')
['y']

先ほどの r'\\\'' (正規表現パターン文字列)の場合、

  1. r'' をはずすと、 \\ -> \\ で、\' -> \'
  2. r'' をはずした、正規表現への入力は、 \\\'
  3. 正規表現で、はじめの \\ が \ で、 \' が ' と解釈される ('はエスケープする必要がないが、正規表現シンタックスと一致しない \ を無視するため)
  4. 文字列 \' と一致したものを出力

raw 文字列でない '\\\\\'' (正規表現パターン文字列)の場合、

  1. '' をはずすと、 \\ -> \ , \\ -> \, \' -> '
  2. '' をはずした、正規表現への入力は、 \\'
  3. 正規表現で、はじめの \\ が \ で、 ' が ' と解釈される ('はエスケープする必要がない)
  4. 文字列 \' と一致したものを出力

raw 文字列の制約

はじめて知ったのですが、raw 文字列には以下の制約があります。
Python リファレンスマニュアルの文字列リテラルの章を見ると、
しっかり書かれています。

接頭文字 "r" または "R" がある場合、バックスラッシュの後にくる文字はそのまま文字列中に入り、バックスラッシュは全て文字列中に残されます。例えば、文字列リテラル r"\n" は二つの文字: バックスラッシュと小文字の "n" からなる文字列を表すことになります。引用符はバックスラッシュでエスケープすることができますが、バックスラッシュ自体も残ってしまいます; 例えば、r"\"" は不正でない文字列リテラルで、バックスラッシュと二重引用符からなる文字列を表します; r"\" は正しくない文字列リテラルです (raw 文字列を奇数個連なったバックスラッシュで終わらせることはできません)。厳密にいえば、 (バックスラッシュが直後のクオート文字をエスケープしてしまうため) raw 文字列を単一のバックスラッシュで終わらせることはできない ということになります。また、バックスラッシュの直後に改行がきても、行継続を意味するのではなく 、それら二つの文字として解釈されるので注意してください。

raw 文字列には、

  • 奇数個の \ で文字列を終わらせることができない
  • "" の中で " をエスケープして \" とすると、バックスラッシュも出力してしまう

という制約があるのです。

Tips

' や " を含む複雑な正規表現では、' や " のエスケープ処理のためにパターン文字列が複雑になってしまいます。
正規表現の中に ' を含む場合は "" で囲み、 " を含む場合は '' で囲み、両方含む場合は """ """ で囲むと、
エスケープをあまり気にせずに書けます。

なお、良い指針がありましたら、ぜひ教えてください〜。

posted by id:junya_hayashi