【Python】正規表現(re)の使い方|search・findall・sub・グループ・名前付き

【Python】正規表現(re)の使い方|search・findall・sub・グループ・名前付き Python

正規表現(regular expression)は、「数字が続く部分」「メールアドレスの形」といったパターンで文字列を検索・抽出・置換するための仕組みです。Pythonでは標準のreモジュールを使います。ログから特定の値を抜き出したり、入力の形式をチェックしたり、文字列を一括置換したりと、文字列処理の強力な道具になります。

つまずきやすいのは、パターンはr"..."(生文字列)で書くこと、searchmatchの違い(matchは先頭のみ)、そしてfindallはグループ(かっこ)の有無で戻り値が変わることです。この記事では、実機のPythonで確認しながら、reモジュールの使い方を整理します。

先に結論

  • 検索はre.search(r"パターン", 文字列)。最初に一致した部分を返します(無ければNone)。
  • パターンはr"\d+"のように生文字列r"..."で書きます。
  • searchはどこでも、matchは先頭だけfullmatchは全体一致です。
  • すべて取り出すならre.findallかっこ(グループ)があると戻り値がタプルになる点に注意。
  • 置換はre.sub(r"パターン", "置換後", 文字列)です。
  • 一致部分はm.group()、グループはm.group(1)や名前で取り出せます。

単純な検索・置換なら文字列メソッド(replace・find)で十分なこともあります。結果の整形はf-string、抽出した数字の変換は型変換もあわせて参考になります。

スポンサーリンク

reの基本:search(最初の一致)

もっとも基本的なのがre.search()です。文字列のどこかにパターンが一致する部分を探し、最初に見つかったものを「マッチオブジェクト」として返します。一致しなければNoneです。

search で検索する
import re

m = re.search(r"\d+", "電話は03-1234です")

print(m.group())   # 03(最初に一致した数字の並び)
print(m.span())    # (3, 5)(一致した位置)

# \d は数字、+ は1回以上の繰り返し(\d+ で「数字が続く部分」)

実機でも、re.search(r"\d+", "電話は03-1234です")は最初の数字の並び03を見つけ、span()でその位置(3, 5)も取得できました。\dは「数字1文字」、+は「直前を1回以上繰り返す」という意味で、合わせて\d+は「数字が続く部分」を表します。検索結果の文字列そのものはm.group()で取り出します。

パターンは生文字列r””で書く

正規表現では\d\wのようにバックスラッシュを多用します。ところが、通常の文字列では\は特別な意味を持つため、パターンはr"..."(raw文字列)で書くのがお約束です。

生文字列 r"" を使う
import re

# 推奨: r"..." で書く(バックスラッシュをそのまま扱える)
re.search(r"\d+", text)
re.search(r"\bword\b", text)

# 非推奨: r を付けないと、\b などが別の意味になったり警告が出たりする
#   "\d" は たまたま動くが、"\b" は バックスペース文字になってしまう
パターンは必ず生文字列 r”…” で

通常の文字列では、\nが改行、\bがバックスペースのように、バックスラッシュが特別な意味を持ちます。正規表現の\b(単語の境界)などを通常の文字列で書くと、意図と違う文字になってしまいます。r"\d+"のようにrを先頭に付けると、バックスラッシュがそのままの文字として扱われ、正規表現を正しく書けます。実際、rを付けずに"\d"と書くと、新しいPythonではSyntaxWarning: invalid escape sequenceという警告が出ます。正規表現のパターンは、迷わずr"..."で書くと覚えておきましょう。

search・match・fullmatchの違い

一致を調べる関数には3つあり、どこを見るかが違います。searchはどこでも、matchは先頭だけ、fullmatchは全体が一致するかを調べます。

search / match / fullmatch
import re

# search: 文字列のどこかに一致すればOK
re.search(r"\d+", "abc123")   # マッチする(123)

# match: 先頭が一致するかだけ
re.match(r"\d+", "123abc")    # マッチする
re.match(r"\d+", "abc123")    # None(先頭が数字でない)

# fullmatch: 文字列全体が一致するか
re.fullmatch(r"\d+", "12345")  # マッチする
re.fullmatch(r"\d+", "123a")   # None(全体が数字ではない)

実機でも、match"abc123"に対してNone(先頭が数字でない)を返し、"123abc"には一致しました。fullmatch"123a"に対してNone(全体が数字でない)でした。「形式チェック」にはfullmatch(入力全体が郵便番号の形か、など)、「含まれているか」にはsearchを使うのが基本です。matchは先頭しか見ないため、用途を間違えないよう注意します。

すべて取り出す findall(グループの罠)

一致する部分をすべてリストで取り出すのがre.findall()です。便利ですが、パターンにかっこ(グループ)があるかどうかで戻り値が変わるという、間違えやすい仕様があります。

findall とグループの違い
import re

# グループ(かっこ)なし → 一致した文字列のリスト
re.findall(r"\d+", "a1b22c333")      # ['1', '22', '333']

# グループ(かっこ)あり → グループ部分のタプルのリスト
re.findall(r"(\d)(\d)", "12 34")    # [('1', '2'), ('3', '4')]
#  → 一致全体ではなく、かっこの中身がタプルで返る
findallはグループがあると戻り値がタプルになる

実機で確認したところ、re.findall(r"\d+", "a1b22c333")['1', '22', '333']文字列のリストを返しましたが、グループ(かっこ)を含むre.findall(r"(\d)(\d)", "12 34")[('1', '2'), ('3', '4')]タプルのリストを返しました。findallは、パターンにかっこが1つでもあると「一致全体」ではなく「かっこの中身」を返すという仕様です。「数字全体がほしいのに、なぜか分割されて返ってくる」というときは、不要なかっこが原因のことが多いです。一致全体とグループの両方を細かく扱いたいときは、次に紹介するfinditerでマッチオブジェクトを1つずつ受け取るほうが確実です。

置換 sub・分割 split

パターンに一致する部分を置き換えるのがre.sub()、パターンで分割するのがre.split()です。文字列のreplaceでは難しい「パターンによる」置換・分割ができます。

置換と分割
import re

# sub: 一致部分を置き換える
re.sub(r"\d+", "#", "a1b22c333")    # 'a#b#c#'

# 複数の区切り文字で分割(カンマ・スペース・タブなど)
re.split(r"[,\s]+", "りんご, みかん   ぶどう")
#  → ['りんご', 'みかん', 'ぶどう']

# 後方参照で並べ替え(\1 は1番目のグループ)
re.sub(r"(\d{4})-(\d{2})", r"\2/\1", "2026-06")   # '06/2026'

実機でも、re.sub(r"\d+", "#", "a1b22c333")a#b#c#になりました。re.split()は、[,\s]+(カンマまたは空白が1回以上)のようなパターンで分割でき、複数種類の区切り文字をまとめて扱えます。re.subでは、置換後の文字列に\1\2と書くと一致したグループを参照でき、日付の並べ替えなども行えます。

グループとマッチオブジェクト

パターンの一部をかっこ()で囲むと「グループ」になり、その部分だけを取り出せます。マッチオブジェクトのgroup()で、一致全体や各グループを取得します。

group でグループを取り出す
import re

m = re.search(r"(\d{4})-(\d{2})-(\d{2})", "日付2026-06-27です")

print(m.group(0))   # 2026-06-27(一致全体)
print(m.group(1))   # 2026(1番目のグループ)
print(m.group(2))   # 06
print(m.groups())   # ('2026', '06', '27')(全グループのタプル)

実機でも、group(0)で一致全体2026-06-27group(1)で最初のグループ2026groups()で全グループのタプル('2026', '06', '27')が取得できました。group(0)は一致した文字列全体、group(1)以降は(の出てくる順番のグループです。日付やURLなど、決まった形式から各パーツを取り出すのに便利です。

名前付きグループ・フラグ

グループに名前を付けると、番号ではなく名前で取り出せて読みやすくなります。また、フラグで大文字小文字を無視するなどの動作を指定できます。

名前付きグループとフラグ
import re

# 名前付きグループ (?P<名前>...)
m = re.search(r"(?P<year>\d{4})/(?P<month>\d{2})", "2026/06")
print(m.group("year"))    # 2026
print(m.group("month"))   # 06

# フラグ: re.IGNORECASE で大文字小文字を無視
re.findall(r"abc", "ABC abc Abc", re.IGNORECASE)
#  → ['ABC', 'abc', 'Abc']

# re.MULTILINE で ^ $ を各行に適用、など
名前付きグループとcompileで読みやすく・速く

実機で確認したところ、(?P<year>\d{4})のように名前を付けると、m.group("year")2026名前で取り出せました。グループが増えると番号では分かりにくくなるため、名前付きにすると可読性が上がります。また、re.IGNORECASEフラグを付けると、abcのパターンでABCabcAbcすべてに一致しました。同じパターンを何度も使うときは、pattern = re.compile(r"\d+")あらかじめコンパイルしておくと、pattern.findall(...)のように呼べて、繰り返し使うときに効率的です。

主なメソッドまとめ

reでよく使うものをまとめます。

関数 働き
re.search(p, s) 最初の一致を返す(無ければNone)
re.match(p, s) 先頭が一致するか
re.fullmatch(p, s) 全体が一致するか(形式チェック)
re.findall(p, s) すべての一致をリストで(グループ注意)
re.sub(p, r, s) 一致部分を置換
re.split(p, s) パターンで分割
m.group(n) 一致全体(0)・各グループ(1〜)

よくある失敗

パターンにrを付けない

\bなどが別の文字になります。パターンはr"..."で書きます。

searchとmatchを混同する

matchは先頭だけです。どこかに含まれるかはsearchを使います。

findallの戻り値がタプルで驚く

グループ(かっこ)があるとタプルが返ります。一致全体がほしいならかっこを使わないかfinditerを使います。

一致しないときのNoneを確認しない

一致がないとNoneが返り、m.group()でエラーになります。if m:で確認します。

ファイル名をre.pyにする

標準ライブラリのreと名前が衝突し、import reが自分のファイルを読んでエラーになります。別の名前にします。

よくある質問

QPythonで正規表現を使うには?
A標準のreモジュールをimport reで読み込み、re.search(r"パターン", 文字列)などを使います。パターンはr"\d+"のように生文字列r"..."で書くのが基本です。検索・抽出・置換・分割が行えます。
Qsearchとmatchの違いは何ですか?
Asearchは文字列のどこかに一致すればよく、matchは先頭が一致するかだけを見ます。たとえば"abc123"に対してsearch(r"\d+")123に一致しますが、match(r"\d+")は先頭が数字でないためNoneです。全体が一致するかはfullmatchを使います。
Qfindallの結果がタプルになってしまいます。
Aパターンにグループ(かっこ)が含まれると、findallは一致全体ではなくグループの中身をタプルで返す仕様です。一致した文字列全体がほしいときは、不要なかっこを外すか、re.finditerでマッチオブジェクトを1つずつ受け取ってm.group()を使ってください。
Qパターンになぜrを付けるのですか?
A正規表現では\d\bなどバックスラッシュを多用しますが、通常の文字列ではバックスラッシュが特別な意味を持ちます。r"..."(生文字列)で書くと、バックスラッシュがそのまま扱われ、正規表現を正しく書けます。rなしだと警告が出たり意図と違う動作になったりします。
Qimport reでエラーになります。
A自分のファイル名をre.pyにしていないか確認してください。標準ライブラリのreと名前が衝突し、import reが自分のファイルを読み込んでしまい、has no attribute 'search'のようなエラーになります。ファイル名を別のもの(regex_test.pyなど)に変えれば解決します。

まとめ

  • 検索はre.search(r"パターン", 文字列)。一致が無ければNoneです。
  • パターンは生文字列r"..."で書きます。
  • searchはどこでも、matchは先頭、fullmatchは全体です。
  • findallグループがあるとタプルを返す点に注意します。
  • 置換はre.sub、グループはgroup(n)や名前付き(?P<名前>...)で取り出します。

正規表現は最初は記号が難しく見えますが、re.searchfindallsubの3つと、r"..."で書くこと、グループの取り出し方を押さえれば、実用的なパターン処理の大半をこなせます。複雑なパターンは少しずつ組み立て、m.group()で結果を確認しながら進めると、確実に身につきます。