Python 正規表現(re)チートシート
「数字だけ抜きたい」「日付を分けたい」から引ける逆引き表です。コードはすべて Python 3.12.10 で実行し、出力をそのまま載せています。
結論:探すなら re.search、全部抜くなら re.findall、置換は re.sub、入力の形チェックは re.fullmatch。パターンは r"..." で書く。
1. 逆引き早見表
「結果」は Python 3.12.10 で実行した出力です。
| やりたいこと | 書くコード | 結果 |
|---|---|---|
| 数字を全部抜く | re.findall(r"\d+", "注文番号A-1234、数量 5、単価 980円") | ['1234', '5', '980'] |
| 最初の数字だけ | re.search(r"\d+", "注文番号A-1234、数量 5").group() | 1234 |
| 「円」の前の数字だけ(先読み) | re.findall(r"\d+(?=円)", "単価 980円、数量 5個、送料 500円") | ['980', '500'] |
| メール形式の簡易チェック | bool(re.fullmatch(r"[\w.+-]+@[\w-]+(\.[\w-]+)+", s)) | taro@example.com → Truetaro@example → False |
| 日付を拾う | re.findall(r"\d{4}[-/]\d{1,2}[-/]\d{1,2}", text) | ['2026-09-29', '2026/10/15'] |
| 日付を年月日に分ける | re.search(r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})", "受付 2026-09-29 完了").groupdict() | {'y': '2026', 'm': '09', 'd': '29'} |
| 並べ替えて置換 | re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "2026-09-29") | 29/09/2026 |
| 空白をまとめる | re.sub(r"\s+", " ", "a b \t c\n d") | a b c d |
| 複数の区切りで分割 | re.split(r"[,;\s]+", "a, b;c d") | ['a', 'b', 'c', 'd'] |
| タグを1つずつ(非貪欲) | re.findall(r"<.+?>", html) | ['<b>', '</b>', '<i>', '</i>'] |
| かぎかっこの中だけ | re.findall(r"「(.+?)」", "「はい」と「いいえ」で答える") | ['はい', 'いいえ'] |
| URL を拾う | re.findall(r"https?://[^\s<>\"']+", page) | ['https://pythonland.tech/', 'http://example.com/a?b=1'] |
| 電話番号・郵便番号 | r"0\d{1,4}-\d{1,4}-\d{4}"/r"\d{3}-\d{4}" | ['03-1234-5678', '090-1234-5678']/下の注意 |
import は import re の1行だけです。入力は、日付の text が "受付 2026-09-29、納期 2026/10/15、参考 2026年10月20日"、page が "see https://pythonland.tech/ and http://example.com/a?b=1 now" です。電話番号は "TEL 03-1234-5678 / 090-1234-5678" に当て、html は 3章の例を使いました。拾った日付の計算は日付・時刻チートシートへ。
メールのパターンは打ち間違いを弾く程度で、規格(RFC 5322)の書き方すべてには対応していません。太郎@example.com や a..b@example.com も通りました。実在は確認メールなど別の手段で確かめます。
郵便番号の \d{3}-\d{4} は "〒100-0001 東京都 03-1234-5678" から ['100-0001', '234-5678'] を返し、電話番号の一部まで拾いました。入力値の判定は re.fullmatch で全体を見ます($ の落とし穴は失敗④)。
2. re の関数7つの使い分け
| 関数 | 返すもの | 使いどころ |
|---|---|---|
match | Match か None(先頭で一致時) | 行頭の形を見る |
search | Match か None(最初の1か所) | 含まれているか・最初の1つ |
fullmatch | Match か None(全体が一致時) | 入力値の形式チェック |
findall | list(文字列かタプル) | 全部抜き出す |
finditer | Match のイテレータ | 位置も知りたい |
sub | 置換後の str(件数つきは subn) | 置換・整形 |
split | list | 複数種類の区切りで分割 |
re.match・re.search・re.fullmatch の違い
r"\d+" が一致したか(bool())の結果です。
| 関数 | "123" | "123abc" | "abc123" |
|---|---|---|---|
match | True | True | False |
search | True | True | True |
fullmatch | True | False | False |
findall はグループの数で戻り値が変わる
text = "税込 1,200円と 3,400円"
print(re.findall(r"\d+,\d+円", text)) # → ['1,200円', '3,400円'] グループなし
print(re.findall(r"(\d+,\d+)円", text)) # → ['1,200', '3,400'] 1個=そのグループ
print(re.findall(r"(\d+),(\d+)円", text)) # → [('1', '200'), ('3', '400')] 2個以上=タプル
print(re.findall(r"(?:\d+,\d+)円", text)) # → ['1,200円', '3,400円'] (?:...) は数えない
まとめたいだけのかっこは非キャプチャの (?:...) にします。位置が要るなら finditer で、"10個、20個、300個" から m.span() が (0, 2)・(4, 6)・(8, 11) と取れました。
sub は関数も渡せる・split は区切りも残せる
print(re.sub(r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})", r"\g<y>年\g<m>月\g<d>日", "2026-09-29")) # → 2026年09月29日
print(re.sub(r"\d+", lambda m: str(int(m.group()) * 2), "3個と10個")) # → 6個と20個
print(re.subn(r"\d+", "#", "3個と10個")) # → ('#個と#個', 2)
print(re.sub(r"\d+", "#", "1 2 3 4", count=2)) # → # # 3 4
置換先には \1・\g<name> のほか関数も渡せます。split はキャプチャがあると区切りも残り、re.split(r"(,)", "a,b,c") は ['a', ',', 'b', ',', 'c'] でした。
count・maxsplit・flags はキーワード引数で書きます。sub・subn・split で位置引数として渡す書き方は 3.13 で非推奨になりました。
str のメソッドで済むなら使わない
固定の文字の置換や1種類の区切りなら "2026-09-29".replace("-", "/") や "a,b,c".split(",") が読みやすく安全です(基本文法の「文字列操作」)。ただし "123".isdigit() は全角でも True なので、半角数字の判定は re.fullmatch(r"[0-9]+", s) です。
3. メタ文字・貪欲と非貪欲・raw 文字列・フラグ
| 記号 | 意味 | 記号 | 意味 |
|---|---|---|---|
. | 改行以外の1文字 | \d | 数字(既定は全角も) |
^ $ | 先頭・末尾($ は末尾の改行の手前にも一致) | \w | 英数字と _(既定は日本語も) |
* + ? | 0回以上・1回以上・0か1回 | \s | 空白・改行(既定は全角スペースも) |
{m,n} | m〜n 回({m} はちょうど m 回) | \b | 単語の境目 |
[abc] [^abc] | どれか1文字・それ以外の1文字 | | | または |
(...) | グループ | \ | 記号を文字として扱う |
(?=...) (?<=...) | 後ろ/前が … のときだけ(その部分は含めない) | \A \Z | 文字列の先頭・末尾(改行を特別扱いしない) |
貪欲と非貪欲(? で最短)
html = "<b>太字</b>と<i>斜体</i>"
print(re.findall(r"<.+>", html)) # → ['<b>太字</b>と<i>斜体</i>']
print(re.findall(r"<.+?>", html)) # → ['<b>', '</b>', '<i>', '</i>']
print(re.findall(r"<[^>]+>", html)) # → ['<b>', '</b>', '<i>', '</i>']
* + はできるだけ長く一致します(貪欲)。? を足すと最短に。4行目のように「止めたい文字以外」を [^...] で書くと意図がはっきりします。
raw 文字列(r"...")を付ける理由
x = "\d+" # SyntaxWarning: invalid escape sequence '\d'
print(repr("\b"), repr(r"\b")) # → '\x08' '\\b'
print(re.findall("\bcat\b", "cat")) # → []
print(re.findall(r"\bcat\b", "cat")) # → ['cat']
Python の文字列も \ を特別扱いするためです。"\b" はバックスペースになり、警告も出ずに一致しなくなります。"\d" の警告は 3.12 で SyntaxWarning に格上げされ、将来は SyntaxError になる予定です。
フラグ
| フラグ | 効果 |
|---|---|
re.IGNORECASE(re.I) | 大文字小文字を無視 |
re.MULTILINE(re.M) | ^ $ が各行に効く。3行の文字列で ^\w+ が ['first'] → ['first', 'second', 'third'] |
re.DOTALL(re.S) | . が改行にも一致 |
re.VERBOSE(re.X) | 空白を無視し # でコメントを書ける |
re.ASCII(re.A) | \d \w \s \b を半角だけに |
複数は | でつなぎ、re.findall(r"^python", "Python\npython", re.IGNORECASE | re.MULTILINE) は ['Python', 'python'] でした。
pat = re.compile(r"""
(?P<user>[\w.+-]+) # ユーザー名
@
(?P<domain>[\w-]+(\.[\w-]+)+) # ドメイン
""", re.VERBOSE)
m = pat.fullmatch("taro@example.co.jp")
print(m.group("user"), m.group("domain")) # → taro example.co.jp
4. グループと re.compile を使うべきとき
m = re.search(r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})", "受付 2026-09-29 完了")
print(m.group("y"), m.group("m"), m.group("d")) # → 2026 09 29
print(m.groupdict()) # → {'y': '2026', 'm': '09', 'd': '29'}
print(m.groups()) # → ('2026', '09', '29')
print(m.span()) # → (3, 13)
(?P<名前>...) で名前を付けると番号を数えずに取り出せます。一致しなかったグループは None で、re.match(r"(\d+)(円)?", "100").groups() は ('100', None) でした。
後方参照 \1 は「1番目のグループと同じ文字列」です。re.findall(r"(\w)\1", "aabbcd") は ['a', 'b'] でした。
re.compile は何度も使うときだけでよい
「
re.compile()やモジュールレベルのマッチング関数に渡された最新のパターンはコンパイル済みのものがキャッシュされるので、一度に正規表現を少ししか使わないプログラムでは正規表現をコンパイルする必要はありません。」(re — 正規表現操作、© Python Software Foundation)
同じパターンの search 10万回は re.search: 0.054s / compiled: 0.030s で、差は約0.024秒。数回呼ぶ程度なら、速度は compile の理由になりにくい差です(1回の計測)。選ぶのは次のときです。
- 同じパターンをループで何万回も使う
- パターンに名前を付けて見通しを良くしたい(
EMAIL.fullmatch(s)のように読める)
5. よくある失敗6つ
① match が先頭しか見ていない
print(re.match(r"\d+", "abc123")) # → None
print(re.search(r"\d+", "abc123")) # → <re.Match object; span=(3, 6), match='123'>
公式ドキュメントも、MULTILINE を付けても match は文字列の先頭だけを見ると注意しています。3.15 で加わった別名 re.prefixmatch() は FAQ へ。
② 記号のエスケープ忘れ
print(re.findall(r"a.c", "abc a.c a-c")) # → ['abc', 'a.c', 'a-c']
print(re.findall(r"a\.c", "abc a.c a-c")) # → ['a.c']
print(re.escape("price (税込) 1.5+")) # → price\ \(税込\)\ 1\.5\+
. + ( を文字として探すなら \ を付けます。ユーザーの入力を検索語にするときは re.escape() で記号をまとめて無効化します。
③ 日本語と \d・\w・\s
print(re.findall(r"\d+", "全角123と半角456")) # → ['123', '456']
print(re.findall(r"\d+", "全角123と半角456", re.ASCII)) # → ['456']
print(re.findall(r"\w+", "Python 3.12 と 正規表現、テスト_01")) # → ['Python', '3', '12', 'と', '正規表現', 'テスト_01']
print(re.split(r"\s+", "a b c")) # → ['a', 'b', 'c'](全角スペースでも分割)
print(re.findall(r"\bPython\b", "Python入門 pythonland Python3")) # → []
\d \w \s は既定で Unicode 全体が相手で、\w はひらがな・漢字・カタカナ(長音「ー」も)に一致しました。半角だけなら re.ASCII か [0-9] で書きます。全角数字は int() にも通る(int(re.search(r"\d+", "全角123").group()) は 123)ので、エラーにならずに混ざります。
最後の行が空なのは、「入」も「3」も \w なので境目にならないためです。かなは [ぁ-ん]+・[ァ-ヶー]+ で拾えます。
④ $ が末尾の改行の手前に一致する
print(re.search(r"^\d+$", "123\n")) # → <re.Match object; span=(0, 3), match='123'>
print(re.fullmatch(r"\d+", "123\n")) # → None
ファイルから読んだ行は末尾に改行が付いていることが多いので、入力チェックは fullmatch(または末尾を \Z)にします。
⑤ None に .group() を呼ぶ
re.match(r"\d+", "abc").group() は AttributeError: 'NoneType' object has no attribute 'group' になりました。if m: で一致を確かめてから呼びます(読み方はエラー辞典の AttributeError)。
⑥ パターンの書き間違いで re.error
try:
re.compile("(")
except re.error as e:
print(e) # → missing ), unterminated subpattern at position 0
r"+abc" なら nothing to repeat at position 0 です。入力されたパターンを使うアプリでは except re.error で受けて表示します。3.13 で re.PatternError に改名されましたが、re.error の名前も残っています(3.12.10 には PatternError がありません)。
6. 正規表現を使っているアプリで見る
| アプリ | 使っている関数・機能 | 何のため |
|---|---|---|
| ログファイルアナライザー | 名前付きグループ入りのパターン定義。改造課題で re.match+groupdict() | ログ形式ごとのパターンの定義例(本体の色分けは文字列の包含判定) |
| ファイル一括リネーマー | re.sub+except re.error | 入力したパターンでファイル名を置換。不正なパターンはエラー表示 |
| シンタックスハイライト付きエディタ | finditer+re.DOTALL、非貪欲 <!--.*?--> | 一致の位置を色付けの範囲に変換 |
| Markdownエディター | re.MULTILINE、キャプチャ付き split、\1 の sub | 見出しの判定と **太字** の置換 |
| 正規表現テスター | compile+finditer・subn・split | 1回コンパイルし、選んだモードで使う |
7. 自分のパターンを試す
正規表現テスターで一致箇所を目で確かめてからコードに移すと失敗が減ります。
- モードは4つ:ハイライト/置換(件数つき)/分割/グループ抽出
- フラグはチェックボックス:i・m・s・u(u は既定と同じ動きで、付けても変わらない)
- サンプル14パターン(メール・URL・日付・郵便番号など)
パターン欄の文字列はそのまま re.compile に渡るので、\ は二重にしません(r"..." の中身を打つ)。
次の2つはサンプルのパターンを Python 3.12.10 のコードで実行した結果です。メールは半角限定で 太郎@example.com は通りません。コメント行の ^\s*#.*$ を "a = 1\n\n# comment\nb = 2" に当てると、m なしで []、ありで ['\n# comment'] となり、直前の空行の改行まで含みました。
8. よくある質問(FAQ)
Q. メールアドレスを正規表現で正確に検証できますか?
規格の書き方すべてを1本で扱うのは現実的ではありません。この記事のパターンは簡易チェックで、日本語や連続ドットも通ります。実在は確認メールなど別の手段で確かめます。
Q. 正規表現と str のメソッドはどちらを使えばいいですか?
固定の文字の置換・1種類の区切りなら str.replace・str.split です。「数字が続く」「区切りが複数種類」など形で指定したいときに正規表現を使います。
Q. Python 3.15 で re.match は使えなくなりますか?
使えます。3.15(2026-10-09 リリース)で同じ動作の別名 re.prefixmatch() が加わり、match は soft deprecated(新規コードでは別名を勧める扱い・警告なし・削除予定なし)になりました。3.14 以前でも動かすコードは match のままで構いません(正式版の What's New と re のドキュメントで確認)。詳しくは3.15 の変更点へ。
本記事は、生成AIを活用して下書きし、運営者が内容を確認・編集したうえで公開しています。AIの利用方針は免責事項をご覧ください。
9. まとめと次の一歩
- 探す=
search、全部=findall、置換=sub、形の判定=fullmatch。matchは先頭だけ - パターンは
r"..."。"\b"は警告なしで壊れる *+は貪欲。最短なら?を足すか[^...]で止める\d\w\sは全角・日本語にも一致。半角だけならre.ASCIIか[0-9]
まずは正規表現テスターで早見表の行を自分のデータに当ててください。re の位置づけはライブラリ一覧、作るものは初心者向け・中級者向けのアプリ一覧へ。
仕様は公式ドキュメント日本語版の re — 正規表現操作/正規表現 HOWTO、版ごとの変更は What's New 3.12/3.13/3.14、3.15 は What's New 3.15(正式版・2026-10-12 確認)を参照。出力はすべて Python 3.12.10(Windows 11)で実行した結果です。