正規表現
正規表現(regex)はテキストパターンを記述するための特別な言語です。正規表現 1 行で、手動コード数十行分のテキストマッチングと抽出ができます。データクリーニング、ログ分析、フォーム検証のいたるところで使われています。
1. 正規表現とは
正規表現は、特殊記号を使って「どのような文字列を探しているか」を記述します。
PYTHON
import re
# 最も簡単な正規表現:文字列を直接マッチ
pattern = r"hello"
text = "hello world"
result = re.search(pattern, text)
if result:
print("Found!") # Found!
print(result.group()) # hello
print(result.start()) # 0(開始位置)
print(result.end()) # 5(終了位置)
⚠️ 正規表現の文字列には必ず
r(raw 文字列)を付けてください。 r"\n" はバックスラッシュ+文字 n であり、改行ではありません。正規表現はバックスラッシュを多用します。r がないと \\n と書く必要があり、非常に面倒です。
2. よく使われるメソッド
PYTHON
import re
text = "My email is alice@example.com, also bob@test.com"
# search() — 最初のマッチを見つける
result = re.search(r"\w+@\w+\.\w+", text)
print(result.group()) # alice@example.com
# findall() — すべてのマッチを見つける
emails = re.findall(r"\w+@\w+\.\w+", text)
print(emails) # ['alice@example.com', 'bob@test.com']
# match() — 先頭からマッチ
print(re.match(r"My", text)) # Match(先頭にある)
print(re.match(r"email", text)) # None(先頭にない)
# sub() — 置換
masked = re.sub(r"\w+@", "***@", text)
print(masked) # My email is *@example.com, also *@test.com
3. メタ文字クイックリファレンス
| メタ文字 | 意味 | 例 | マッチするもの |
|---|---|---|---|
. |
任意の 1 文字(改行除く) | h.t |
hat, hot, hit |
\d |
数字 | \d{3} |
123, 456 |
\w |
英字/数字/アンダースコア | \w+ |
hello, abc123 |
\s |
空白 | \s |
スペース、タブ、改行 |
* |
直前の文字の 0 回以上の繰り返し | ab*c |
ac, abc, abbc |
+ |
直前の文字の 1 回以上の繰り返し | ab+c |
abc, abbc(ac は不可) |
? |
直前の文字の 0 回または 1 回 | colou?r |
color, colour |
{n} |
正確に n 回 | \d{4} |
2026, 1990 |
{n,m} |
n 回から m 回 | \d{2,4} |
23, 456, 2026 |
^ |
文字列の先頭 | ^Hello |
Hello... |
$ |
文字列の末尾 | end$ |
...end |
[] |
文字セット | [aeiou] |
任意の母音 |
| ` | ` | OR | `cat |
例:電話番号の検証(難易度 ⭐⭐)
PYTHON
import re
def is_valid_phone(phone):
"""中国の携帯電話番号を検証(11 桁、1 で始まる)"""
pattern = r"^1[3-9]\d{9}$"
return bool(re.match(pattern, phone))
phones = ["13800138000", "12345678901", "010-12345678", "1380013800a"]
for p in phones:
print(f"{p}: {'✅' if is_valid_phone(p) else '❌'}")
# テキストからすべての電話番号を抽出
text = "Contact: 13800138000, backup: 13912345678, landline: 010-12345678"
phones = re.findall(r"1[3-9]\d{9}", text)
print(f"Found phones: {phones}")
4. グループキャプチャ
括弧 () を使って、マッチした内容を複数の部分に分割します:
PYTHON
import re
# メールからユーザー名とドメインを抽出
email = "alice@example.com"
pattern = r"(\w+)@(\w+\.\w+)"
result = re.search(pattern, email)
if result:
print(f"Full match: {result.group(0)}") # alice@example.com
print(f"Username: {result.group(1)}") # alice
print(f"Domain: {result.group(2)}") # example.com
# ログから情報を抽出
log = "2026-06-23 15:30:45 ERROR Database connection timeout"
pattern = r"(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.+)"
result = re.search(pattern, log)
print(f"Date: {result.group(1)}") # 2026-06-23
print(f"Time: {result.group(2)}") # 15:30:45
print(f"Level: {result.group(3)}") # ERROR
print(f"Message: {result.group(4)}") # Database connection timeout
5. 貪欲マッチと遅延マッチ
デフォルトでは * と + は貪欲(greedy)で、できるだけ多くマッチします。? を追加すると遅延(lazy)になり、できるだけ少なくマッチします:
PYTHON
import re
text = "<h1>Title</h1><p>Paragraph</p>"
# 貪欲モード — できるだけ多くマッチ
greedy = re.search(r"<.+>", text)
print(greedy.group()) # <h1>Title</h1><p>Paragraph</p>
# 遅延モード — できるだけ少なくマッチ(? を追加)
lazy = re.search(r"<.+?>", text)
print(lazy.group()) # <h1>
💡
.*?(遅延)vs .*(貪欲): HTML や JSON のような構造化コンテンツを扱う場合、遅延モードの方が一般的です。貪欲モードはしばしば「越境」して、マッチすべきでない内容までマッチします。迷ったら遅延から始めましょう。
よくあるユースケース
- フォーム検証:メールアドレス、電話番号、ID 番号のフォーマット検証
- データ抽出:Web ページ、ログ、テキストから特定のパターンを抽出
- テキスト置換:パターンに一致する内容を一括置換(例:電話番号のマスキング)
- シンタックスハイライト:エディタは正規表現でキーワードや文字列を識別
- ログ分析:大量のログファイルから特定のパターンをフィルタリング・処理
❓ よくある質問
Q 正規表現の
r プレフィックスは何を意味しますか?A
r は raw 文字列を示します。通常の文字列では \n は改行ですが、raw 文字列では \n は単なるバックスラッシュ+文字 n です。正規表現はバックスラッシュを多用します(\d、\w など)。r がないと \\d と書く必要があり、非常に面倒です。Q 正規表現はどの程度パフォーマンスが良いですか?
A ほとんどのケースで十分高速です。ただし、不適切に書かれたパターン(ネストした量指定子
(.*)* など)は「壊滅的なバックトラッキング」を引き起こし、単純なテキストでも数分かかることがあります。対策:ネストした量指定子を避け、遅延モードを使用し、頻繁に使うパターンは re.compile() を使います。Q 複雑な正規表現は読みにくいです。何かコツはありますか?
A ① コメントを追加——
re.VERBOSE モードで正規表現内にスペースとコメントを許可。② 複数の単純なパターンに分割。③ regex101.com のようなオンラインツールでデバッグ。正規表現は古典的な「書くのは楽しいが、読むのは苦痛」——1 つの正規表現ですべてを解決しようとしないでください。📖 まとめ
re.search()で最初のマッチ。findall()ですべてのマッチ。sub()で置換- メタ文字:
\d数字、\w単語構成文字、\s空白、.任意の文字 - 量指定子:
*任意、+1 以上、?0 または 1、{n}正確に n 回 - グループ
()で部分を抽出。group(1)で最初のグループを取得 - 貪欲はできるだけ多くマッチ。
?を追加して遅延(できるだけ少なく) - バックスラッシュ地獄を避けるため、正規表現には常に
rプレフィックスを付ける
📝 練習問題
-
基本(難易度 ⭐):関数
is_valid_email(email)を書いてください。正規表現を使ってメールアドレスを検証します(@を含む、両側が空でない、ドメインサフィックスがある)。 -
中級(難易度 ⭐⭐):関数
extract_numbers(text)を書いてください。文字列からすべての数値(整数と小数を含む)を抽出し、その合計を返します。例:"Price is 19.99, shipping 5, coupon -10"→ 14.99 を返します。 -
上級(難易度 ⭐⭐⭐):「簡易テンプレートエンジン」を書いてください。テンプレート
"Hello, {name}! Your order {order_id} has been shipped, arriving in {days} days."と辞書{"name": "Alice", "order_id": "2024001", "days": 3}が与えられたとき、正規表現ですべての{variable}プレースホルダを実際の値に置き換えます。ヒント:コールバック関数とともにre.sub()を使用。



