Python: 正規表現

正規表現(regex)はテキストパターンを記述するための特別な言語です。正規表現 1 行で、手動コード数十行分のテキストマッチングと抽出ができます。データクリーニング、ログ分析、フォーム検証のいたるところで使われています。


1. 正規表現とは

正規表現は、特殊記号を使って「どのような文字列を探しているか」を記述します。

PYTHON
import re

# 最も簡単な正規表現:stringを直接マッチ
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}")
▶ 試してみよう

出力:

TEXT 📖 参照専用
# 函数定义成功


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

▶ サンプル:URLパーサー(難易度 ⭐⭐⭐)

PYTHON
import re

def parse_url(url):
    """URLを解析して、プロトコル、ドメイン、パス、クエリパラメータを抽出します"""
    pattern = r"(https?)://([^/]+)(/[^?]*)?(?:\?([^#]*))?"
    result = re.search(pattern, url)
    if not result:
        return None
    
    return {
        "protocol": result.group(1),
        "domain": result.group(2),
        "path": result.group(3) or "/",
        "query": result.group(4) or ""
    }

urls = [
    "https://www.example.com/path/page.html?id=123&name=test",
    "http://localhost:8080/api/users",
    "https://google.com"
]

for url in urls:
    info = parse_url(url)
    if info:
        print(f"\nURL: {url}")
        for key, value in info.items():
            print(f"  {key}: {value}")
▶ 試してみよう

出力:

TEXT 📖 参照専用
# 函数定义成功


5. 貪欲マッチと遅延マッチ

(3) ▶ サンプル:テキスト置換とマスキング (難易度 ⭐⭐)

PYTHON
import re

text = "User Alice's phone is 13800138000, email is alice@example.com, ID 110101199001011234"

phone_masked = re.sub(r"(1[3-9]\d)\d{4}(\d{4})", r"\1****\2", text)
print(f"Phone masked: {phone_masked}")

email_masked = re.sub(r"(\w{2})\w+(@\w+\.\w+)", r"\1***\2", text)
print(f"Email masked: {email_masked}")

id_masked = re.sub(r"(\d{4})\d{10}(\d{4})", r"\1**********\2", text)
print(f"ID masked: {id_masked}")

template = "Hello, {name}! Order {order_id} has been shipped."
data = {"name": "Alice", "order_id": "ORD2024001"}
result = re.sub(r"\{(\w+)\}", lambda m: data.get(m.group(1), m.group(0)), template)
print(f"Template result: {result}")
▶ 試してみよう

出力:

TEXT 📖 参照専用
Phone masked: User Alice's phone is 138****8000, email is alice@example.com, ID 110101199001011234
Email masked: User Alice's phone is 13800138000, email is al***@example.com, ID 110101199001011234
ID masked: User Alice's phone is 13800138000, email is alice@example.com, ID 1101**********1234
Template result: Hello, Alice! Order ORD2024001 has been shipped.


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



6. よくあるユースケース


❓ よくある質問

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() を使用。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%