第13章 正規表現
正規表現とは?
正規表現(regular expression、略してregexやregexp)は、文字列のパターンを表現するための強力な言語ツールです。これにより、文字列の検索、置換、検証などの操作を行うことができます。
Pythonでは、reという標準ライブラリを使用して正規表現の操作を行います。
正規表現の基本的な構成要素
- リテラル: 通常の文字として解釈される文字です。例:aや1。
- メタキャラクタ: 正規表現の動作を制御する特別な文字。例:.や*。
- エスケープ: メタキャラクタをリテラルとして解釈するために、\を使用します。例:\.は実際の.文字を意味します。
Pythonでの正規表現の基本的な使い方
re.search()関数: 文字列内で正規表現に一致する部分を検索します。
re.match()関数: 文字列の先頭が正規表現に一致するかを確認します。
re.findall()関数: 文字列内で正規表現に一致するすべての部分をリストとして返します。
基本的なパターン
メタキャラクタは正規表現内で特別な意味を持つ文字のことを指します。
[]を使用してキャラクタクラスを定義します。これにより、指定された範囲の文字のいずれか1文字と一致します。
以下は特別なシーケンスの例です。
量指定子
量指定子は、直前の文字やグループがどれだけの回数繰り返すかを指定するためのメタ文字です。以下に、Pythonの正規表現で使用される主な量指定子を示します。
位置指定
正規表現で位置を指定するメタ文字やシーケンスを使用すると、特定の位置にマッチするパターンを作成することができます。以下に、Pythonの正規表現で使用される主な位置指定のメタ文字やシーケンスについて詳しく説明します。
グルーピングとキャプチャ
正規表現の中で部分的なマッチを取り出したり、その部分に対して特定の操作を適用する場合、グルーピングとキャプチャが非常に便利です。以下に、Pythonの正規表現におけるグルーピングとキャプチャに関する詳細を説明します。
グルーピング
グルーピングは、正規表現内で括弧()を使用して行います。括弧内の部分表現は1つのグループとして扱われ、このグループ内でのマッチは後から個別に取り出すことができます。
キャプチャ
グルーピングによって作成されたグループは、マッチの結果から個別に取り出すことができます。この取り出しを「キャプチャ」と呼びます。上の例で見たように、group()メソッドを使用して特定のグループをキャプチャできます。
キャプチャの省略
時には、グルーピングを行いたくてもその結果をキャプチャしたくないことがあります。このような場合、?:を括弧の先頭に追加することでキャプチャを無効にできます。
名前付きグループ
マッチの結果を取り出すときに、インデックス番号ではなく名前を使用して結果を取得することができます。これは、特に複雑な正規表現や多数のグループが存在する場合に役立ちます。
前方/後方の否定
正規表現における前方/後方の否定は、あるパターンの前や後に特定の文字やシーケンスが存在しないことを確認する際に使用されます。Pythonのreモジュールでは、前方/後方の否定のアサーションをサポートしています。
前方の否定 (Negative Lookbehind)
前方の否定は、現在の位置の前にあるテキストが指定したパターンに一致しないことを確認します。この否定の構文は(?<!pattern)で、patternは一致させたくないパターンです。
次の正規表現は、"ing" で終わる単語を検索しますが、"ing" の前に "str" が来てはならないという条件を持っています。
この例では、"singing" はマッチしますが、"stringing" はマッチしません。
後方の否定 (Negative Lookahead)
後方の否定は、現在の位置の後にあるテキストが指定したパターンに一致しないことを確認します。この否定の構文は(?![pattern])で、patternは一致させたくないパターンです。
次の正規表現は、"price" の後に "high" が続かないテキストを検索します。
この例では、最後の "price" はマッチしますが、"price high" の部分はマッチしません。
フラグとオプション
正規表現を使う際には、振る舞いを変更するためのさまざまなフラグやオプションを使用できます。これによって、大文字小文字を無視したマッチングや、複数行モードなどの特定の動作を簡単に実現することができます。
re.IGNORECASE (または re.I)
大文字と小文字を区別せずにマッチングを行います。
re.MULTILINE (または re.M)
文字列が複数行にわたる場合に、^と$がそれぞれ各行の開始と終了にマッチするようになります。
re.DOTALL (または re.S)
このフラグを使用すると、.(ドット)が改行文字\nにもマッチします。
re.VERBOSE (または re.X)
このフラグを使うと、正規表現の中に空白やコメントを入れることができ、読みやすい正規表現を書くことができます。
re.ASCII (または re.A)
\w,\b,\s,\dなどのエスケープシーケンスがASCII文字のみにマッチするようになります。デフォルトでは、これらはユニコード文字にもマッチします。
これらのフラグは、コンパイル時や検索時に使うことができます。複数のフラグを組み合わせて使用することもできます。例えば、re.compile(pattern, re.IGNORECASE | re.MULTILINE)のようにビットORで連結して使用します。
応用的なテクニック
正規表現はテキストの検索や操作において非常に強力なツールであり、その機能性は基本的なものから非常に高度なものまで様々です。ここでは、Pythonでの正規表現の応用的なテクニックについて説明します。
ルックアヘッドとルックビハインド
正のルックアヘッド:先読みとも呼ばれ、あるパターンの後に続く別のパターンを検出しますが、そのパターン自体は結果には含まれません。
負のルックアヘッド:あるパターンの後に続かない別のパターンを検出します。
正のルックビハインド:後ろ読みとも呼ばれ、あるパターンの前に存在する別のパターンを検出します。
負のルックビハインド:あるパターンの前に存在しない別のパターンを検出します。
条件付き正規表現
特定の条件下でのみマッチするパターンを作成できます。
名前付きキャプチャ
グルーピングは便利ですが、インデックス番号でアクセスするのではなく、名前でアクセスするとさらにわかりやすくなります。
フラグを使用した複数行のマッチ
re.MULTILINEやre.DOTALLなどのフラグを使用すると、複数行のテキストにまたがるマッチを行うことができます。
実践例
正規表現は非常に強力で、さまざまなテキスト処理のタスクにおいて役立ちます。以下に、Pythonのreモジュールを使用した実践的な正規表現の例をいくつか紹介します。
Eメールアドレスの検出
Eメールアドレスをテキストから検出するための簡単な正規表現です。
URLの検出
URLを検出する正規表現の一例です。
日付の検出
以下は、YYYY-MM-DD形式の日付を検出する正規表現の例です。
HTMLタグの除去
HTMLタグを除去してプレーンテキストのみを取得するための正規表現です。
電話番号の検出
以下は、様々なフォーマットの電話番号を検出する正規表現の例です。
最適化とパフォーマンス
正規表現はテキスト処理に非常に強力ですが、複雑なパターンや大量のテキストデータを扱う際にはパフォーマンスの問題が発生する可能性があります。以下は、Pythonの正規表現を効果的に利用するための最適化とパフォーマンスに関するヒントです。
コンパイル済み正規表現
re.compile()を使用することで、同じ正規表現を繰り返し使用する場合の実行時間を短縮できます。この関数は、正規表現のパターンを事前にコンパイルして、後で再利用できる正規表現オブジェクトを返します。
非キャプチャグループ
キャプチャ不要な場合は、非キャプチャグループを使用してメモリ使用量を削減することができます。
貪欲マッチングと非貪欲マッチング
デフォルトで、量指定子は「貪欲」です。可能な限り最長のマッチを試みます。非貪欲マッチ(最短マッチ)を行いたい場合は、量指定子の後に?を追加します。
固定文字列の検索
正規表現の機能が不要な場合(単なる部分文字列の検索など)、inキーワードやstr.find()、str.startswith()などの組み込み関数を使用した方が高速に動作します。
パターンの最適化
[^...]よりも[...]を使用する方が高速です。可能であれば、^(行の先頭)や$(行の終端)を利用して、検索範囲を制限します。
パフォーマンスの計測
実際のコードでパフォーマンス問題が発生しているかどうかを確認するために、timeitモジュールを使用して実行時間を計測します。
注意点とトラブルシューティング
正規表現は非常に強力なツールですが、使い方によっては予期しない結果やパフォーマンスの問題が発生することもあります。以下は、Pythonでの正規表現使用時の注意点とトラブルシューティングについての概要です。
バックスラッシュ \ の使用
Pythonの文字列リテラル内でバックスラッシュを使用する際は、エスケープが必要です。しかし、正規表現にもバックスラッシュを使用することが多いため、これが混同を招く原因となります。
貪欲マッチ
正規表現の量指定子はデフォルトで貪欲に動作します。これが原因で意図しない長いマッチを取得することがあります。
カタストロフィックバックトラッキング
複雑な正規表現や矛盾したパターンを使用すると、非常に遅い実行時間となることがあります。
マルチラインモード
^や$はデフォルトで文字列の先頭や末尾にマッチしますが、マルチラインのテキストを処理する際には、各行の先頭や末尾にマッチさせたい場合があります。
ユニコードとの互換性
\wや\dなどのショートカットシーケンスは、ASCII文字だけでなくユニコード文字にもマッチすることがあります。
マッチしない場合のデバッグ
正規表現が期待通りにマッチしない場合、どこに問題があるのかを特定するのが難しいことがあります。
練習問題1.
以下のテキストから日本の電話番号を抜き出してください。日本の電話番号は、市外局番が3桁-4桁または2桁-4桁、市内局番が4桁、加えて、伸ばし棒がある場合も考慮してください。
テキスト:
練習問題2.
以下のテキストから正しい形式のメールアドレスだけを抜き出してください。
テキスト:
練習問題3.
以下のテキストからhttpまたはhttpsを使用したURLを抜き出してください。
テキスト: