正規表現

正規表現(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 のような構造化コンテンツを扱う場合、遅延モードの方が一般的です。貪欲モードはしばしば「越境」して、マッチすべきでない内容までマッチします。迷ったら遅延から始めましょう。


よくあるユースケース


❓ よくある質問

Q 正規表現の r プレフィックスは何を意味しますか?
A r は raw 文字列を示します。通常の文字列では \n は改行ですが、raw 文字列では \n は単なるバックスラッシュ+文字 n です。正規表現はバックスラッシュを多用します(\d\w など)。r がないと \\d と書く必要があり、非常に面倒です。
Q 正規表現はどの程度パフォーマンスが良いですか?
A ほとんどのケースで十分高速です。ただし、不適切に書かれたパターン(ネストした量指定子 (.*)* など)は「壊滅的なバックトラッキング」を引き起こし、単純なテキストでも数分かかることがあります。対策:ネストした量指定子を避け、遅延モードを使用し、頻繁に使うパターンは re.compile() を使います。
Q 複雑な正規表現は読みにくいです。何かコツはありますか?
A ① コメントを追加——re.VERBOSE モードで正規表現内にスペースとコメントを許可。② 複数の単純なパターンに分割。③ regex101.com のようなオンラインツールでデバッグ。正規表現は古典的な「書くのは楽しいが、読むのは苦痛」——1 つの正規表現ですべてを解決しようとしないでください。

📖 まとめ


📝 練習問題

  1. 基本(難易度 ⭐):関数 is_valid_email(email) を書いてください。正規表現を使ってメールアドレスを検証します(@ を含む、両側が空でない、ドメインサフィックスがある)。

  2. 中級(難易度 ⭐⭐):関数 extract_numbers(text) を書いてください。文字列からすべての数値(整数と小数を含む)を抽出し、その合計を返します。例:"Price is 19.99, shipping 5, coupon -10" → 14.99 を返します。

  3. 上級(難易度 ⭐⭐⭐):「簡易テンプレートエンジン」を書いてください。テンプレート "Hello, {name}! Your order {order_id} has been shipped, arriving in {days} days." と辞書 {"name": "Alice", "order_id": "2024001", "days": 3} が与えられたとき、正規表現ですべての {variable} プレースホルダを実際の値に置き換えます。ヒント:コールバック関数とともに re.sub() を使用。

100%