Pythonにおけるreモジュールと正規表現の実践的活用

Pythonで文字列のパターンマッチングや抽出を行う際、標準ライブラリであるreモジュールが非常に強力なツールとなります。本稿では、reモジュールの基本的な使い方から、フラグを活用した高度なマッチングまでを解説します。

基本的なテキスト抽出

以下のようなログデータから、特定のフォーマットを持つエラーコード(例: ERR-XXXXX)を抽出したい場合を考えます。

2023-10-01 10:00:00 INFO System started
2023-10-01 10:05:23 ERROR ERR-48291 Database connection failed
2023-10-01 10:10:45 WARN Disk space low
2023-10-01 10:15:12 ERROR ERR-93012 Timeout occurred

標準的な文字列操作でも実現可能ですが、reモジュールを使えばより簡潔に記述できます。

import re

log_data = """2023-10-01 10:00:00 INFO System started
2023-10-01 10:05:23 ERROR ERR-48291 Database connection failed
2023-10-01 10:10:45 WARN Disk space low
2023-10-01 10:15:12 ERROR ERR-93012 Timeout occurred"""

# 従来の文字列操作による抽出
error_codes_manual = []
for line in log_data.splitlines():
    parts = line.split()
    if len(parts) >= 4 and parts[2] == "ERROR":
        error_codes_manual.append(parts[3])

print(error_codes_manual)  # ['ERR-48291', 'ERR-93012']

# reモジュールを使用した抽出
error_codes_regex = re.findall(r"ERR-\d{5}", log_data)
print(error_codes_regex)  # ['ERR-48291', 'ERR-93012']

マッチングの基本とグループ化

正規表現の強力な機能の一つが「グループ化」です。丸括弧 () を使用してパターンをグループ化し、特定の部分を抽出できます。

import re

# 任意の1文字にマッチ
match_any = re.search(r".", "hello")
print(match_any.group())  # 'h'

# グループ化と交互マッチング
text = "version_2.1_release"
pattern = r"version_(\d)\.(\d)_(alpha|beta|release)"
match_ver = re.search(pattern, text)
if match_ver:
    print(match_ver.group())   # 'version_2.1_release'
    print(match_ver.group(1))  # '2'
    print(match_ver.group(3))  # 'release'

# マッチしない場合の処理
no_match = re.search(r"version_3", text)
print(no_match)  # None

名前付きグループによるデータ抽出

複雑な文字列から複数の要素を抽出する際、名前付きグループ (?P<name>pattern) を使用すると、コードの可読性が大幅に向上します。ここではISO 8601形式のタイムスタンプをパースする例を見てみましょう。

import re

timestamp = "2023-11-15T08:30:45Z"

# 名前付きグループを使用したパターン
ts_pattern = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})T(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})Z"
match_ts = re.search(ts_pattern, timestamp)

if match_ts:
    # 辞書形式でグループを取得
    time_dict = match_ts.groupdict()
    print(time_dict)
    # {'year': '2023', 'month': '11', 'day': '15', 'hour': '08', 'minute': '30', 'second': '45'}
    
    # 特定のグループへのアクセス
    print(f"Year: {match_ts.group('year')}, Month: {match_ts.group('month')}")

文字列の分割、置換、および完全一致

reモジュールは、検索だけでなく文字列の操作にも有用な関数を提供しています。

import re

# \d を使用した数字のマッチング
print(re.search(r"\d+", "Item42").group())  # '42'

# re.split() による分割
csv_line = "apple,banana,,cherry"
# 連続するカンマや単一のカンマで分割
print(re.split(r",+", csv_line))  # ['apple', 'banana', 'cherry']

# re.sub() による置換
# 連続する空白を単一のスペースに置換
messy_text = "Python   is    awesome"
clean_text = re.sub(r"\s+", " ", messy_text)
print(clean_text)  # 'Python is awesome'

# re.fullmatch() による完全一致(文字列全体がパターンに一致する場合のみ)
print(re.fullmatch(r"[a-z]+", "hello"))    # Match object
print(re.fullmatch(r"[a-z]+", "hello123")) # None

# re.match() は文字列の先頭からのマッチのみを検証
print(re.match(r"\d+", "123abc"))  # Match object ('123')
print(re.match(r"\d+", "abc123"))  # None

パターンのコンパイルと再利用

同じ正規表現パターンを繰り返し使用する場合、re.compile() でパターンをコンパイルしておくことで、パフォーマンスの向上とコードの整理が図れます。

import re

# パターンのコンパイル
email_pattern = re.compile(r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+")

# コンパイルしたパターンオブジェクトのメソッドを直接呼び出し
text = "Contact us at support@example.com or info@test.org"
emails = email_pattern.findall(text)
print(emails)  # ['support@example.com', 'info@test.org']

# search や match も利用可能
first_email = email_pattern.search(text)
print(first_email.group())  # 'support@example.com'

フラグ(Flags)による動作の拡張

フラグを指定することで、正規表現のデフォルトの動作を変更できます。フラグは各関数の引数として渡すか、コンパイル時に指定します。

import re

# re.IGNORECASE (re.I): 大文字・小文字を区別しない
print(re.search(r"python", "Learn PYTHON", re.I))  # Match object

# re.MULTILINE (re.M): 複数行モード。^ と $ が各行の先頭と末尾にマッチ
multiline_text = """first line
second line
third line"""

# 通常の ^ は文字列の先頭にのみマッチ
print(re.findall(r"^second", multiline_text))      # []
# re.M を指定すると各行の先頭にマッチ
print(re.findall(r"^second", multiline_text, re.M))  # ['second']

# re.DOTALL (re.S): ドット (.) が改行文字 (\n) にもマッチするようになる
html_snippet = "<div>\n  Content\n</div>"
print(re.search(r"<div>.*</div>", html_snippet))       # None
print(re.search(r"<div>.*</div>", html_snippet, re.S))  # Match object

# re.VERBOSE (re.X): 正規表現内にコメントや空白を挿入し、可読性を高める
# r"" (raw string) を使用してエスケープ文字の解釈を抑制
ip_pattern = re.compile(r"""
    \b              # ワード境界
    (?:\d{1,3}\.)   # 最初の3オクテットとドット (非キャプチャグループ)
    {3}             # 上記の繰り返し
    \d{1,3}         # 最後のオクテット
    \b              # ワード境界
""", re.X)

test_ip = "Server IP is 192.168.1.100."
print(ip_pattern.search(test_ip).group())  # '192.168.1.100'

タグ: Python re 正規表現 文字列操作 パターンマッチング

8月15日 03:57 投稿