中学英語808712 views
中学理科1626207 views
MathPython491378 views
小学社会308636 views
世界の国560595 views
小学理科717236 views
数学講師2852771 views
高校生物549842 views
LaTeX957300 views
高校倫理1433119 views
Help
Tools

English

Python 正規表現のグループ化とキャプチャ

Claude にきいてみた。

Python の正規表現で () を使うとグループ化できるって聞いたけど、どういうこと?

正規表現における括弧 () には 2 つの役割がある。1 つは複数の文字をまとめて扱う「グループ化」、もう 1 つはマッチした部分を取り出す「キャプチャ」だ。

たとえば (ab)+ と書くと、ab という 2 文字のかたまりが 1 回以上繰り返すパターンを表せる。括弧がなければ ab+ は「a の後に b が 1 回以上」という別の意味になってしまう。

(ab)+

ab, abab, ababab … にマッチ

ab+

ab, abb, abbb … にマッチ

キャプチャの機能を使うと、マッチした文字列の一部だけを抜き出せる。re.searchre.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'}

グループが多くなると番号で管理するのは大変になる。パターンを変更したときに番号がずれるリスクもあるため、実務では名前付きグループを積極的に使うとよいだろう。