正規表現(regular expression)は、「数字が続く部分」「メールアドレスの形」といったパターンで文字列を検索・抽出・置換するための仕組みです。Pythonでは標準のreモジュールを使います。ログから特定の値を抜き出したり、入力の形式をチェックしたり、文字列を一括置換したりと、文字列処理の強力な道具になります。
つまずきやすいのは、パターンはr"..."(生文字列)で書くこと、searchとmatchの違い(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です。
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文字列)で書くのがお約束です。
import re # 推奨: r"..." で書く(バックスラッシュをそのまま扱える) re.search(r"\d+", text) re.search(r"\bword\b", text) # 非推奨: r を付けないと、\b などが別の意味になったり警告が出たりする # "\d" は たまたま動くが、"\b" は バックスペース文字になってしまう
通常の文字列では、\nが改行、\bがバックスペースのように、バックスラッシュが特別な意味を持ちます。正規表現の\b(単語の境界)などを通常の文字列で書くと、意図と違う文字になってしまいます。r"\d+"のようにrを先頭に付けると、バックスラッシュがそのままの文字として扱われ、正規表現を正しく書けます。実際、rを付けずに"\d"と書くと、新しいPythonではSyntaxWarning: invalid escape sequenceという警告が出ます。正規表現のパターンは、迷わずr"..."で書くと覚えておきましょう。
search・match・fullmatchの違い
一致を調べる関数には3つあり、どこを見るかが違います。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()です。便利ですが、パターンにかっこ(グループ)があるかどうかで戻り値が変わるという、間違えやすい仕様があります。
import re
# グループ(かっこ)なし → 一致した文字列のリスト
re.findall(r"\d+", "a1b22c333") # ['1', '22', '333']
# グループ(かっこ)あり → グループ部分のタプルのリスト
re.findall(r"(\d)(\d)", "12 34") # [('1', '2'), ('3', '4')]
# → 一致全体ではなく、かっこの中身がタプルで返る
実機で確認したところ、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()で、一致全体や各グループを取得します。
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-27、group(1)で最初のグループ2026、groups()で全グループのタプル('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 で ^ $ を各行に適用、など
実機で確認したところ、(?P<year>\d{4})のように名前を付けると、m.group("year")で2026と名前で取り出せました。グループが増えると番号では分かりにくくなるため、名前付きにすると可読性が上がります。また、re.IGNORECASEフラグを付けると、abcのパターンでABC・abc・Abcすべてに一致しました。同じパターンを何度も使うときは、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が自分のファイルを読んでエラーになります。別の名前にします。
よくある質問
reモジュールをimport reで読み込み、re.search(r"パターン", 文字列)などを使います。パターンはr"\d+"のように生文字列r"..."で書くのが基本です。検索・抽出・置換・分割が行えます。searchは文字列のどこかに一致すればよく、matchは先頭が一致するかだけを見ます。たとえば"abc123"に対してsearch(r"\d+")は123に一致しますが、match(r"\d+")は先頭が数字でないためNoneです。全体が一致するかはfullmatchを使います。findallは一致全体ではなくグループの中身をタプルで返す仕様です。一致した文字列全体がほしいときは、不要なかっこを外すか、re.finditerでマッチオブジェクトを1つずつ受け取ってm.group()を使ってください。\dや\bなどバックスラッシュを多用しますが、通常の文字列ではバックスラッシュが特別な意味を持ちます。r"..."(生文字列)で書くと、バックスラッシュがそのまま扱われ、正規表現を正しく書けます。rなしだと警告が出たり意図と違う動作になったりします。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.search・findall・subの3つと、r"..."で書くこと、グループの取り出し方を押さえれば、実用的なパターン処理の大半をこなせます。複雑なパターンは少しずつ組み立て、m.group()で結果を確認しながら進めると、確実に身につきます。

