こんにちは、かつコーチです。
「メールアドレスの形式をチェックしたい」「文字列から特定のパターンだけ抜き出したい」。
こうした処理をinやsplitだけで書こうとすると、コードが複雑になりがちです。
そこで役立つのが正規表現です。
正規表現とは、文字列のパターンを記号を使って表現する仕組みで、Pythonではreモジュールを使って扱います。
reモジュールは標準ライブラリなので、追加インストールなしでimport reとするだけで使えます。
この記事では、reモジュールの主要な関数の違いと、実務でよく使うパターンを解説します。
reモジュールの基本操作
match・search・findallの違い
reモジュールには、文字列を検索する関数がいくつかあります。
それぞれ挙動が異なるため、最初に違いを整理しておきましょう。
import re
text = "電話番号は090-1234-5678です"
pattern = r"\d{3}-\d{4}-\d{4}"
# match: 文字列の先頭からのみマッチを試みる
result_match = re.match(pattern, text)
print(result_match) # None(先頭が数字ではないため)
# search: 文字列全体から最初の1件だけを探す
result_search = re.search(pattern, text)
print(result_search.group()) # 090-1234-5678
# findall: 文字列全体からすべてのマッチをリストで取得する
result_findall = re.findall(pattern, text)
print(result_findall) # ['090-1234-5678']
re.match()は文字列の先頭からしかマッチを判定しません。
そのため、途中にパターンがあってもNoneが返る点に注意が必要です。
re.search()は文字列のどこにあってもマッチを1件見つけてくれます。re.findall()は該当するすべての箇所をリストとして返すため、複数件抽出したいときに使います。
基本的なパターン記法
正規表現でよく使う記法を、実例とあわせて紹介します。
import re
# \d: 数字1文字
print(re.findall(r"\d", "abc123")) # ['1', '2', '3']
# \d+: 数字が1文字以上連続
print(re.findall(r"\d+", "abc123def456")) # ['123', '456']
# [a-zA-Z]+: 英字の連続
print(re.findall(r"[a-zA-Z]+", "abc123def")) # ['abc', 'def']
# .*: 任意の文字が0文字以上
print(re.findall(r"a.*z", "a123z")) # ['a123z']
# ^と$: 文字列の先頭・末尾
print(re.match(r"^abc", "abcdef")) # マッチする
\dは数字、+は「直前の文字が1回以上繰り返す」という意味です。[a-zA-Z]のような角かっこは「この範囲のいずれか1文字」を表します。
文字列を置換する(re.sub)
パターンにマッチした部分を別の文字列に置き換えるにはre.sub()を使います。
import re
text = "電話番号は090-1234-5678です"
result = re.sub(r"\d{3}-\d{4}-\d{4}", "[非公開]", text)
print(result)
# 電話番号は[非公開]です
第1引数にパターン、第2引数に置換後の文字列、第3引数に対象の文字列を渡します。
個人情報のマスキングや、不要な空白文字の削除など、実務でよく使う処理です。
グループ化で部分を取り出す
かっこ()で囲むと、マッチした文字列の一部分だけを取り出せます。
これをグループ化と呼びます。
import re
text = "2026-08-22"
pattern = r"(\d{4})-(\d{2})-(\d{2})"
match = re.search(pattern, text)
if match:
year, month, day = match.groups()
print(f"{year}年{month}月{day}日")
# 2026年08月22日
match.groups()を使うと、各グループのマッチ結果をタプルで取得できます。
日付や住所など、構造を持った文字列を分解したいときに便利です。
よくあるつまずきポイント・エラー対処
matchとsearchを混同してマッチしない
Before(つまずいたコード)
import re
log_line = "[ERROR] 2026-08-22 処理に失敗しました"
pattern = r"\[ERROR\]"
result = re.match(pattern, log_line)
print(result)
このコード自体は動きますが、私は別のログ行で似たコードを書いていてハマったことがあります。
log_line = "ログ出力: [ERROR] 処理に失敗しました"
result = re.match(pattern, log_line)
print(result)
# None
行の先頭に[ERROR]がない場合、re.match()はNoneを返してしまいます。
「マッチしているはずなのに条件分岐に入らない」というバグの原因になり、原因究明に時間がかかりました。
After(改善したコード)
import re
log_line = "ログ出力: [ERROR] 処理に失敗しました"
pattern = r"\[ERROR\]"
result = re.search(pattern, log_line)
if result:
print("エラー行を検出しました")
文字列の途中にパターンがある可能性がある場合は、re.match()ではなくre.search()を使いましょう。
「先頭一致で十分か、文中のどこかにあればよいか」を意識して使い分けることが大切です。
エスケープ忘れで意図しない記号にマッチする
Before(つまずいたコード)
import re
text = "価格は1000円です"
pattern = r"\d+.円" # ドットをそのままの意味で使ったつもり
result = re.search(pattern, text)
print(result.group() if result else "マッチなし")
正規表現では.は「任意の1文字」を意味する特殊記号です。
「.(ピリオド)そのもの」を検索したいつもりでも、意図せず別の1文字にもマッチしてしまいます。
マッチした文字列: 1000円
一見正しく見えますが、.が「円の前の任意の1文字」にマッチしているだけで、本来意図した「ピリオド」の検索にはなっていません。
After(改善したコード)
import re
text = "価格は1000.5円です"
pattern = r"\d+\.\d+円" # ドットをエスケープして「.そのもの」として扱う
result = re.search(pattern, text)
print(result.group() if result else "マッチなし")
# 1000.5円
.や(など、正規表現で特別な意味を持つ記号をそのまま検索したい場合は、バックスラッシュ\でエスケープします。
特殊記号かどうか迷ったら、Pythonの公式ドキュメントで意味を確認する習慣をつけましょう。
まとめ
この記事のポイント
re.match()は先頭一致、re.search()は文中一致、re.findall()は全件取得です\dや+、[]などの基本記法を組み合わせてパターンを作りますre.sub()でマッチした部分を置換できます()によるグループ化で、マッチの一部だけを取り出せます.のような特殊記号を文字通り検索したい場合は\でエスケープします
次に読むべき記事
ファイルパスの操作をより現代的に書きたい方は、pathlibの解説記事もあわせてご覧ください。
CSVファイルの読み書きに正規表現を組み合わせたい方は、csvモジュールの記事も参考になります。
タグ: Python, 中級者向け, 標準ライブラリ