サイトアイコン アマチュア無線局JS2IIU

【Python】正規表現:reモジュールで文字列を効率的に処理する

こんにちは、JS2IIUです。
正規表現(Regular Expression)は、文字列の検索やパターンマッチングを行うための強力なツールです。Pythonではreモジュールを使用することで、複雑な文字列処理を効率的に実行することができます。今回もよろしくお願いします。

1. reモジュールの基本

まず最初に、reモジュールをインポートする必要があります:

Python
import re

主な関数

reモジュールには以下の重要な関数があります:

2. パターンマッチングの基本

2.1 単純な文字列マッチング

Python
# 基本的な文字列マッチング
text = "Hello, Python!"
result = re.search(r"Python", text)
print(result.group())  # 出力: Python

# マッチしない場合
result = re.search(r"Java", text)
print(result)  # 出力: None

2.2 メタ文字の使用

Python
# ドット(.): 任意の1文字にマッチ
text = "cat, bat, rat"
result = re.findall(r".at", text)
print(result)  # 出力: ['cat', 'bat', 'rat']

# アスタリスク(*): 直前の文字の0回以上の繰り返し
text = "ca*t"
result = re.match(r"ca*t", text)
print(result.group())  # 出力: cat

3. よく使用するパターン

3.1 数字のマッチング

Python
# 数字にマッチする
text = "私の電話番号は123-4567-8901です"
result = re.search(r"\d{3}-\d{4}-\d{4}", text)
print(result.group())  # 出力: 123-4567-8901

# 数字以外にマッチする
text = "ABC123"
result = re.findall(r"\D+", text)
print(result)  # 出力: ['ABC']

3.2 メールアドレスのバリデーション

Python
def is_valid_email(email):
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))

# テスト
print(is_valid_email("user@example.com"))  # True
print(is_valid_email("invalid.email@"))    # False

4. グループの使用

4.1 キャプチャグループ

Python
# 括弧()でグループを作成
text = "2024年2月19日"
pattern = r"(\d{4})(\d{1,2})(\d{1,2})"
result = re.match(pattern, text)

if result:
    year = result.group(1)
    month = result.group(2)
    day = result.group(3)
    print(f"年: {year}, 月: {month}, 日: {day}")
    # 出力: 年: 2024, 月: 2, 日: 19

4.2 名前付きグループ

Python
# ?P<name>でグループに名前をつける
text = "名前: 山田太郎, 年齢: 30歳"
pattern = r"名前: (?P<name>.*?), 年齢: (?P<age>\d+)"
result = re.search(pattern, text)

if result:
    print(f"名前: {result.group('name')}")  # 出力: 名前: 山田太郎
    print(f"年齢: {result.group('age')}")   # 出力: 年齢: 30

5. 文字列の置換

5.1 基本的な置換

Python
# 単純な置換
text = "Hello, World!"
result = re.sub(r"World", "Python", text)
print(result)  # 出力: Hello, Python!

# 複数箇所の置換
text = "cat and cat"
result = re.sub(r"cat", "dog", text)
print(result)  # 出力: dog and dog

5.2 条件付き置換

Python
def convert_case(match):
    word = match.group(0)
    return word.upper() if len(word) > 3 else word

text = "The cat and dog are friends"
result = re.sub(r'\w+', convert_case, text)
print(result)  # 出力: THE cat and dog ARE FRIENDS

6. パフォーマンスの最適化

6.1 パターンのコンパイル

頻繁に使用するパターンは、コンパイルして再利用することでパフォーマンスが向上します:

Python
# パターンのコンパイル
pattern = re.compile(r'\d+')

# 複数回の使用
text1 = "123 456"
text2 = "789 012"

print(pattern.findall(text1))  # 出力: ['123', '456']
print(pattern.findall(text2))  # 出力: ['789', '012']

6.2 最適化のヒント

7. よくある間違いと注意点

7.1 メタ文字のエスケープ

Python
# 正しいエスケープ
text = "price: $100"
result = re.search(r'\$\d+', text)
print(result.group())  # 出力: $100

# ドット文字のエスケープ
text = "file.txt"
result = re.search(r'file\.txt', text)
print(result.group())  # 出力: file.txt

7.2 貪欲な量指定子と非貪欲な量指定子

Python
text = "<p>First paragraph</p><p>Second paragraph</p>"

# 貪欲なマッチング
greedy = re.findall(r'<p>.*</p>', text)
print(greedy)  # 出力: ['<p>First paragraph</p><p>Second paragraph</p>']

# 非貪欲なマッチング
non_greedy = re.findall(r'<p>.*?</p>', text)
print(non_greedy)  # 出力: ['<p>First paragraph</p>', '<p>Second paragraph</p>']

8. 貪欲マッチングと非貪欲マッチングの詳細説明

正規表現における貪欲(greedy)マッチングと非貪欲(non-greedy)マッチングの違いを理解することは、効果的なパターンマッチングを行う上で非常に重要です。

8.1 貪欲マッチングとは

貪欲マッチングは、量指定子(*, +, ?, {n,m})がデフォルトで持つ動作で、「できるだけ多くの文字にマッチする」という特徴があります。

Python# 貪欲マッチングの例 text = "<h1>タイトル</h1><h2>サブタイトル</h2>" pattern = r"<.*>" result = re.search(pattern, text) print(result.group()) # 出力: <h1>タイトル</h1><h2>サブタイトル</h2> # 最初の'<'から最後の'>'まですべてマッチしてしまう 8.2 非貪欲マッチングとは

非貪欲(最小)マッチングは、量指定子の後に?を付けることで実現され、「できるだけ少ない文字でマッチする」という特徴があります。

Python# 非貪欲マッチングの例 text = "<h1>タイトル</h1><h2>サブタイトル</h2>" pattern = r"<.*?>" result = re.findall(pattern, text) print(result) # 出力: ['<h1>', '</h1>', '<h2>', '</h2>'] # 最小限の文字数でマッチング 8.3 実践的な例

HTMLタグの抽出

Python
html = """
<div class="content">
    <p>最初の段落</p>
    <p>次の段落</p>
</div>
"""

# 貪欲マッチング - 意図しない結果
greedy = re.findall(r'<p>.*</p>', html)
print("貪欲マッチング:")
print(greedy)  
# 出力: ['<p>最初の段落</p>\n    <p>次の段落</p>']

# 非貪欲マッチング - 期待通りの結果
non_greedy = re.findall(r'<p>.*?</p>', html)
print("非貪欲マッチング:")
print(non_greedy)  
# 出力: ['<p>最初の段落</p>', '<p>次の段落</p>']

引用符で囲まれた文字列の抽出

Python
text = '"First quote" some text "Second quote" more text "Third quote"'

# 貪欲マッチング - 問題のある結果
greedy = re.findall(r'".*"', text)
print("貪欲マッチング:")
print(greedy)  
# 出力: ['"First quote" some text "Second quote" more text "Third quote"']

# 非貪欲マッチング - 正しい結果
non_greedy = re.findall(r'".*?"', text)
print("非貪欲マッチング:")
print(non_greedy)  
# 出力: ['"First quote"', '"Second quote"', '"Third quote"']

8.4 量指定子と貪欲/非貪欲の対応

各量指定子の貪欲版と非貪欲版の対応表:

Python
# 量指定子の例
text = "aaabbaaa"

# *(0回以上)
print(re.findall(r'a*', text))      # 貪欲: ['aaa', '', '', 'aaa', '']
print(re.findall(r'a*?', text))     # 非貪欲: ['', '', '', '', '', '', '', '', '']

# +(1回以上)
print(re.findall(r'a+', text))      # 貪欲: ['aaa', 'aaa']
print(re.findall(r'a+?', text))     # 非貪欲: ['a', 'a', 'a', 'a', 'a', 'a']

# ?(0回または1回)
print(re.findall(r'ab?', text))     # 貪欲: ['a', 'ab', 'a', 'a', 'a']
print(re.findall(r'ab??', text))    # 非貪欲: ['a', 'a', 'b', 'a', 'a', 'a']

# {n,m}(n回以上m回以下)
print(re.findall(r'a{1,3}', text))  # 貪欲: ['aaa', 'aaa']
print(re.findall(r'a{1,3}?', text)) # 非貪欲: ['a', 'a', 'a', 'a', 'a', 'a']

8.5 実装上の注意点

  1. パフォーマンスの考慮
  • 非貪欲マッチングは、バックトラッキングが多く発生する可能性があり、パフォーマンスに影響を与えることがあります
  • 可能な場合は、より具体的なパターンを使用することを検討してください
Python
# 非効率な非貪欲マッチング
text = "a" * 1000 + "b"
pattern = r"a*?b"
# マッチするまでに多数のバックトラッキングが発生

# より効率的な代替パターン
pattern = r"[^b]*b"
# 否定文字クラスを使用することで、バックトラッキングを削減
  1. 適切なパターン選択
  • 必ずしも非貪欲マッチングが最適解とは限りません
  • 文脈に応じて、否定文字クラス[^...]や、より具体的な文字クラスの使用を検討してください
Python
# HTMLタグの抽出の別アプローチ
text = "<p>段落1</p><p>段落2</p>"

# 非貪欲マッチングを使用
pattern1 = r'<p>.*?</p>'

# より具体的なパターンを使用
pattern2 = r'<p>[^<]*</p>'

# どちらも同じ結果を得られますが、
# pattern2の方がより効率的で意図が明確です

これらの例で示したように、貪欲マッチングと非貪欲マッチングの適切な使い分けは、正規表現を効果的に活用する上で重要なスキルとなります。特にHTMLやXMLのパース、引用文の抽出など、実際のテキスト処理では頻繁に使用される技術です。

まとめ

Pythonの正規表現は強力な文字列処理ツールですが、適切に使用することが重要です:

  1. 基本的なパターンマッチングから始める
  2. 必要に応じてグループを使用する
  3. パフォーマンスを考慮してパターンをコンパイルする
  4. 適切なエスケープとフラグを使用する
  5. 貪欲/非貪欲マッチングの違いを理解する

参考リンク

最後に、書籍のPRです。
24年9月に出版された「ハイパーモダンPython-信頼性の高いワークフローを構築するモダンテクニック」、Claudio Jolowicz著、嶋田、鈴木訳。開発環境の構築、プロジェクトの管理、テストに関して実践的な内容でとても参考になる一冊です。ぜひ手に取ってみてください。

最後まで読んでいただきありがとうございます。