はじめに
Pythonのreモジュールにおける公式ドキュメントを読んでもしっくり来なかったので、具体的な使い方を含めてまとめてみます。
正規表現の先読みアサーション(Lookahead Assertion), 後読みアサーション(Lookbehind Assertion)についての情報です。
バージョンはPython 3.7.0で確認。
先読みアサーションって何?
正規表現において**(?=...)**で記述出来る、検索条件には含めるけどマッチ対象には含めない記法のこと。
混乱を招きやすい以下2つの表現について説明します。
- (?=...) 先読みアサーション(Lookahead Assersion)
- (?<=...) 後読みアサーション(Lookbehind Assertion)
どんな時に使うの?
具体的には空白文字で挟まれた&&をandに置き換える場合を考えてみます。( && ) => ( and )
re.subを使って全体を対象として置換したいような場合です。
以下の通り重複が発生しないような場合は単純な正規表現で対応可能です。
空白に挟まれた&&をマッチ対象として、単純に空白で挟んだandで置換しています。
pattern = r'\s&&\s'
string = 'foo && bar && buz'
re.sub(pattern, ' and ', string)
>>> 'foo and bar and buz'
しかし重複がある場合は意図通りには動作しません。
下の例では二番目の空白文字(&& &&の間)が重複した状態(overlapped)になっています。
pattern = r'\s&&\s'
string_overlapped = 'foo && && bar'
re.sub(pattern, ' and ', string_overlapped)
>>> 'foo and && bar'
1番目の&&の後ろの空白文字が最初のマッチで消費されてしまって、2番目の&&の前には空白文字が存在しないことになってしまっているために1箇所しか置換されません。
Pythonの標準モジュールであるreの正規表現ではマッチしている部分が重複しないように取り出す(non-overlapping)であるため、このような挙動になります。
そこで先読みアサーションの出番です。
どうやるか
pattern_ex = r'(?<=\s)&&(?=\s)'
string_overlapped = 'foo && && bar'
re.sub(pattern_ex, 'and', string_overlapped)
>>> 'foo and and bar'
空白文字(\s)は判定条件として機能していますが、マッチ対象に含まれません。
そのため置き換え対象の文字列も'and'となり空白文字を取り除いています。
先の例とは異なって空白を除いた&&のみがマッチ対象となっているので、それがandに置き換わっています。
もう少し詳しく先読みアサーションと後読みアサーションについて説明します。
先読みアサーション (?=...)
マッチ対象(この場合は&&)の後ろに続く部分文字列...(この場合は\s)を指定できます。
ポイントとなるのはこの丸括弧で括った部分文字列は判定条件にのみ使われ、マッチ対象には含まれないことです。
この記法には先読みアサーションという名前がついています。
検索対象となっている条件部分を消費せず宣言しているだけなのでアサーション、実際のマッチ対象は前方にあることから先読み(Lookahead)という分類のようです。
後読みアサーション (?<=...)
同じ意味合いで、マッチ対象の前に置かれる部分文字列...(この場合は\s)を指定できます。
丸括弧で括った部分がマッチ対象に含まれない点が同じです。
実際のマッチ対象は後ろに置かれているため後読み(Loobehind)アサーションです。
2つ合わせてLookaround Assertionと呼びます。
他の方法(regexモジュール)
標準モジュールでは無いのですが、regexモジュールにはoverlappedについてのオプションが一部あります。
regex.finditer, regex.findallにはoverlappedオプションがあるのでこれをTrueにすることで重複ありで検索できます。
ただregex.subにはこのオプションはありません。
最後に
Pythonでコードを書いていた時にhelp(re.sub)で調べると「...non-overlapping occurrences of the pattern...」とあるけど、overlappingさせたい場合はどうすればいいんだと混乱してました。
先読みアサーションが使いこなせれば色々と便利です。
正規表現に関してはPythonと関係なくドキュメントは沢山あるのですがピンポイントの情報をまとめてみました