Claude にきいてみた。
Python の正規表現で () を使うとグループ化できるって聞いたけど、どういうこと?
正規表現における括弧 () には 2 つの役割がある。1 つは複数の文字をまとめて扱う「グループ化」、もう 1 つはマッチした部分を取り出す「キャプチャ」だ。
たとえば (ab)+ と書くと、ab という 2 文字のかたまりが 1 回以上繰り返すパターンを表せる。括弧がなければ ab+ は「a の後に b が 1 回以上」という別の意味になってしまう。
ab, abab, ababab ... にマッチ
ab, abb, abbb ... にマッチ
キャプチャの機能を使うと、マッチした文字列の一部だけを抜き出せる。re.search や re.findall と組み合わせて使うのが一般的だ。
具体的なコード例を見せて。
日付文字列から年・月・日をそれぞれ取り出す例を見てみよう。
import re
text = "今日は2025-06-15です"
pattern = r"(\d{4})-(\d{2})-(\d{2})"
match = re.search(pattern, text)
if match:
print(match.group(0)) # マッチ全体: 2025-06-15
print(match.group(1)) # 1番目のグループ: 2025
print(match.group(2)) # 2番目のグループ: 06
print(match.group(3)) # 3番目のグループ: 15
group(0) はマッチ全体を返し、group(1) 以降は括弧で囲んだ部分を左から順に返す。複数の値を一度に取得したい場合は groups() メソッドが便利だ。
year, month, day = match.groups()
print(f"{year}年{month}月{day}日") # 2025年06月15日
re.findall だとどうなる?
re.findall にグループを含むパターンを渡すと、マッチ全体ではなくグループ部分だけがリストで返ってくる。グループが複数ある場合はタプルのリストになる。
import re
text = "会議: 2025-06-15, 締切: 2025-07-01"
pattern = r"(\d{4})-(\d{2})-(\d{2})"
results = re.findall(pattern, text)
print(results)
# [('2025', '06', '15'), ('2025', '07', '01')]
この挙動を知らないと「なぜ日付全体が取れないのか」と混乱しがちなので注意が必要だ。マッチ全体を取得したい場合は、グループを使わないパターンにするか、後述する非キャプチャグループを使う。
非キャプチャグループって何?
(?:...) という書き方をすると、グループ化はするがキャプチャはしない「非キャプチャグループ」になる。量指定子をまとめて適用したいが、結果には含めたくない場合に使う。
import re
text = "color: red, colour: blue"
pattern = r"colou?r: (\w+)"
results = re.findall(pattern, text)
print(results) # ['red', 'blue']
上の例では u? で「u があってもなくてもよい」としているが、もし (ou)? のように括弧を使うと、その部分もキャプチャされてしまう。
# 意図しない結果
pattern_bad = r"col(ou)?r: (\w+)"
results_bad = re.findall(pattern_bad, text)
print(results_bad) # [('', 'red'), ('ou', 'blue')]
非キャプチャグループを使えば、グループ化しつつキャプチャを避けられる。
# 非キャプチャグループを使用
pattern_good = r"col(?:ou)?r: (\w+)"
results_good = re.findall(pattern_good, text)
print(results_good) # ['red', 'blue']
グループに名前を付けることもできる?
(?P<name>...) という構文で名前付きグループを作れる。番号ではなく名前でアクセスできるため、複雑なパターンでも可読性が高まる。
import re
text = "田中太郎 <tanaka@example.com>"
pattern = r"(?P<name>.+?) <(?P<email>.+?)>"
match = re.search(pattern, text)
if match:
print(match.group("name")) # 田中太郎
print(match.group("email")) # tanaka@example.com
groupdict() メソッドを使うと、名前をキーとした辞書形式で取得できる。
print(match.groupdict())
# {'name': '田中太郎', 'email': 'tanaka@example.com'}
グループが多くなると番号で管理するのは大変になる。パターンを変更したときに番号がずれるリスクもあるため、実務では名前付きグループを積極的に使うとよいだろう。