正規表現の量指定子(*、+、?、{n,m})はデフォルトで「貪欲(greedy)」に動作する。できるだけ長くマッチしようとするのだ。一方、量指定子の後ろに ? を付けると「非貪欲(non-greedy / lazy)」になり、できるだけ短くマッチするようになる。
貪欲マッチの挙動
HTML タグを抽出したい場面を考えてみよう。次のコードは意図どおりに動かない。
import re text = "<p>Hello</p><p>World</p>" pattern = r"<.*>" result = re.findall(pattern, text) print(result)
期待した結果
['<p>', '</p>', '<p>', '</p>'] のように個別のタグを取得したい
実際の結果
['<p>Hello</p><p>World</p>'] と文字列全体がマッチしてしまう
.* は「任意の文字を 0 回以上」という意味だが、貪欲マッチなので最初の < から最後の > まで可能な限り長くマッチする。途中にある > で止まってくれない。
非貪欲マッチで解決する
量指定子の後ろに ? を付けると非貪欲マッチになる。
import re text = "<p>Hello</p><p>World</p>" pattern = r"<.*?>" result = re.findall(pattern, text) print(result) # ['<p>', '</p>', '<p>', '</p>']
.*? は「任意の文字を 0 回以上、ただし最短で」という意味になる。最初の < にマッチしたら、次に > が見つかった時点ですぐにマッチを終える。これで個別のタグを抽出できた。
量指定子ごとの非貪欲版
すべての量指定子に非貪欲版がある。
| 貪欲 | 非貪欲 | 意味 |
|---|---|---|
| * | *? | 0 回以上(最短) |
| + | +? | 1 回以上(最短) |
| ? | ?? | 0 回または 1 回(最短) |
| {n,m} | {n,m}? | n〜m 回(最短) |
実践例:引用符で囲まれた文字列を抽出
CSV やログファイルでよくあるパターンとして、引用符で囲まれた文字列の抽出がある。
import re text = 'name="Alice" age="30" city="Tokyo"' # 貪欲マッチ(失敗例) greedy = re.findall(r'".*"', text) print(greedy) # ['"Alice" age="30" city="Tokyo"'] # 非貪欲マッチ(成功例) lazy = re.findall(r'".*?"', text) print(lazy) # ['"Alice"', '"30"', '"Tokyo"']
貪欲マッチだと最初の " から最後の " まで全部マッチしてしまうが、非貪欲マッチなら各引用符ペアを個別に抽出できる。
非貪欲マッチを使わない別解
非貪欲マッチの代わりに、否定文字クラスを使う方法もある。
import re text = "<p>Hello</p><p>World</p>" # 非貪欲マッチ pattern1 = r"<.*?>" # 否定文字クラス pattern2 = r"<[^>]*>" print(re.findall(pattern1, text)) # ['<p>', '</p>', '<p>', '</p>'] print(re.findall(pattern2, text)) # ['<p>', '</p>', '<p>', '</p>']
[^>]* は「> 以外の文字を 0 回以上」という意味だ。> が現れた時点で必然的にマッチが止まるので、非貪欲マッチと同じ結果になる。パフォーマンス面ではこちらのほうが有利な場合もあるため、状況に応じて使い分けるとよい。