正規表現は、テキスト パターンを照合するための強力な言語です。このページでは、Python の演習に十分な正規表現の基本的な概要と、Python での正規表現の仕組みについて説明します。Python の「re」は、モジュールは正規表現をサポートします。
Python では通常、正規表現検索は次のように記述します。
match = re.search(pat, str)
re.search() メソッドは、正規表現パターンと文字列を受け取り、文字列内でそのパターンを検索します。検索が成功した場合、search() は一致オブジェクトを返し、それ以外の場合は None を返します。そのため、通常は検索の直後に、検索が成功したかどうかをテストする if 文が続きます。次の例では、パターン「word」を検索します。その後に 3 文字の単語が続きます(詳細は下記を参照)。
import re str = 'an example word:cat!!' match = re.search(r'word:\w\w\w', str) # If-statement after search() tests if it succeeded if match: print('found', match.group()) ## 'found word:cat' else: print('did not find')
コード match = re.search(pat, str)
は、「match」という名前の変数に検索結果を格納します。次に、if ステートメントで一致をテストします。true の場合、検索が成功し、match.group() が一致するテキスト(例: 'word:cat')になります。それ以外の場合は、一致が false(より限定的に None)の場合、検索は成功せず、一致するテキストはありません。
「r」Python の「raw」モジュールを文字列で、変更なしでバックスラッシュを通すことができるため、正規表現に非常に便利です(Java にはこの機能が必要です)。パターン文字列は常に「r」を使って記述することを習慣としておいてください。
基本パターン
正規表現の長所は、固定文字だけでなくパターンも指定できることです。1 つの文字に一致する最も基本的なパターンは次のとおりです。
- a、X、9、<-- 通常の文字が正確に一致します。特殊な意味があるためメタ文字が一致しません。メタ文字は次のとおりです。^ $ * + ?{ [ ] \ |( )(詳しくは下記をご覧ください)
- 。(ピリオド) -- 改行「\n」以外の任意の 1 文字に一致します。
- \w --(小文字の w)は「単語」に一致します。文字: 文字、数字、または下線 [a-zA-Z0-9_]。なお、「word」はこれはニモニックで、1 単語ではなく 1 単語の char にのみ一致します。\W(大文字 W)は、単語以外の任意の文字に一致します。
- \b -- 単語と単語以外の境界
- \s --(小文字の s)は 1 つの空白文字(スペース、改行、リターン、タブ、形式 [ \n\r\t\f])に一致します。\S(大文字 S)は、空白以外の文字に一致します。
- \t, \n, \r -- タブ, 改行, リターン
- \d -- 10 進数字 [0-9](一部の古い正規表現ユーティリティは \d をサポートしていませんが、すべて \w と \s をサポートしています)
- ^ = 開始、$ = 終了 -- 文字列の先頭または末尾に一致
- \ -- 「特殊性」を抑制です。たとえば、「\」を使用します。ピリオドまたは \\ はスラッシュに一致します。文字に「@」などの特別な意味があるかどうか不明な場合は、その前にスラッシュ(\@)を入力してみてください。\c のような有効なエスケープ シーケンスでない場合、Python プログラムはエラーで停止します。
基本的な例
ジョーク: 目が 3 つあるブタを何て言う?ピーチ!
文字列内のパターンに対する正規表現検索の基本的なルールは次のとおりです。
- 検索は文字列を最初から最後まで実行し、最初に見つかった一致で終了します。
- 文字列のすべてではなく、すべてのパターンが一致する必要があります
-
match = re.search(pat, str)
が成功した場合、match は None ではなく、特に match.group() が一致するテキストです。
## Search for pattern 'iii' in string 'piiig'. ## All of the pattern must match, but it may appear anywhere. ## On success, match.group() is matched text. match = re.search(r'iii', 'piiig') # found, match.group() == "iii" match = re.search(r'igs', 'piiig') # not found, match == None ## . = any char but \n match = re.search(r'..g', 'piiig') # found, match.group() == "iig" ## \d = digit char, \w = word char match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123" match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"
重複表現
+ と * を使用してパターンの繰り返しを指定すると、より興味深い結果が得られます
- + -- 左側のパターンの 1 回以上の出現。例:「i+」= 1 つまたは複数の i
- * -- 左側に 0 個以上のパターン
- ? -- 左側の 0 個または 1 個のパターンを照合します
左端と最大規模の
まず、左端に一致するパターンを見つけ、2 番目に、文字列をできるだけ多く使用しようとします。つまり、+ と * をできる限り多く使用しようとします(+ と * は「欲張り」と呼ばれます)。
繰り返しの例
## i+ = one or more i's, as many as possible. match = re.search(r'pi+', 'piiig') # found, match.group() == "piii" ## Finds the first/leftmost solution, and within it drives the + ## as far as possible (aka 'leftmost and largest'). ## In this example, note that it does not get to the second set of i's. match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii" ## \s* = zero or more whitespace chars ## Here look for 3 digits, possibly separated by whitespace. match = re.search(r'\d\s*\d\s*\d', 'xx1 2 3xx') # found, match.group() == "1 2 3" match = re.search(r'\d\s*\d\s*\d', 'xx12 3xx') # found, match.group() == "12 3" match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123" ## ^ = matches the start of string, so this fails: match = re.search(r'^b\w+', 'foobar') # not found, match == None ## but without the ^ it succeeds: match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"
メールの例
「xyz alice-b@google.com purple monkey」という文字列内のメールアドレスを検索するとします。これを実行中の例として使用して、より正規表現の機能を説明します。では、r'\w+@\w+' というパターンを試してみましょう。
str = 'purple alice-b@google.com monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print(match.group()) ## 'b@google'
この場合、\w が「-」と一致しないため、検索してもメールアドレス全体は取得されません。または「.」表示されます。この問題は、以下の正規表現機能を使用して修正します。
角かっこ
角かっこは一連の文字を示すのに使用できるため、[abc] は 'a' と一致します。または「b」または「c」を入力します。\w や \s などのコードは角かっこ内でも使用できますが、例外としてドット(.)は単にリテラルのドットを意味します。メールの問題では、角かっこを使用すると「.」を簡単に追加できます。および「-」パターン r'[\w.-]+@[\w.-]+' で @ を囲むことができる文字のセットメールアドレス全体を取得するには、次のように入力します。
match = re.search(r'[\w.-]+@[\w.-]+', str) if match: print(match.group()) ## 'alice-b@google.com'
グループ抽出
「グループ」機能を使用すると、一致するテキストの一部を取り出すことができます。email の問題では、ユーザー名とホストを個別に抽出する必要があります。そのためには、r'([\w.-]+)@([\w.-]+)' のように、パターンでユーザー名とホストをかっこ( )で囲みます。この場合、かっこはパターンに一致するものを変えるのではなく、論理的な「グループ」を確立します。あります。検索が成功した場合、match.group(1) は左の 1 番目のかっこに対応するテキスト、match.group(2) は左の 2 番目のかっこに対応するテキストです。通常の match.group() は、引き続き通常どおりの完全一致のテキストです。
str = 'purple alice-b@google.com monkey dishwasher' match = re.search(r'([\w.-]+)@([\w.-]+)', str) if match: print(match.group()) ## 'alice-b@google.com' (the whole match) print(match.group(1)) ## 'alice-b' (the username, group 1) print(match.group(2)) ## 'google.com' (the host, group 2)
正規表現を使用する一般的なワークフローでは、目的のパターンを記述し、かっこで囲んで必要な部分を抽出します。
ファインドール
findall() はおそらく、re モジュールの中で最も強力な関数でしょう。上記では、re.search() を使用してパターンの最初の一致を検出しました。findall() は、「すべて」の一致を検出し、各文字列が 1 つの一致を表す文字列のリストとして返します。## Suppose we have a text with many email addresses str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher' ## Here re.findall() returns a list of all the found email strings emails = re.findall(r'[\w\.-]+@[\w\.-]+', str) ## ['alice@google.com', 'bob@abc.com'] for email in emails: # do something with each found email string print(email)
ファイルを使って検索
ファイルの場合、ファイルの行を反復するループを記述することが習慣になり、各行で findall() を呼び出すことができます。代わりに、findall() に反復処理を任せる方がはるかに効率的です。ファイルのテキストを findall() にフィードして、1 つのステップですべての一致のリストを返すようにします(f.read() はファイルのテキスト全体を 1 つの文字列で返すことを思い出してください)。
# Open file f = open('test.txt', encoding='utf-8') # Feed the file text into findall(); it returns a list of all the found strings strings = re.findall(r'some pattern', f.read())
findall と Groups
かっこ( )のグループ メカニズムは、findall() と組み合わせることができます。パターンに 2 つ以上のかっこグループが含まれている場合、文字列のリストを返す代わりに、findall() は *タプル*のリストを返します。各タプルはパターンの 1 つの一致を表し、タプルの内部には group(1)、group(2) .. のデータが含まれます。したがって、2 つのかっこのグループがメールパターンに追加されている場合、findall() はタプルのリストを返します。タプルのリストは長さが 2 で、それぞれユーザー名とホストが含まれています。(例: 「alice」、「google.com」)。
str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher' tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str) print(tuples) ## [('alice', 'google.com'), ('bob', 'abc.com')] for tuple in tuples: print(tuple[0]) ## username print(tuple[1]) ## host
タプルのリストを取得したら、ループでタプルごとに計算を実行できます。パターンにかっこが含まれていない場合、findall() は前の例と同様に、見つかった文字列のリストを返します。パターンに 1 組のかっこが含まれている場合、findall() は 1 つのグループに対応する文字列のリストを返します。(あいまいなオプション機能: パターンにかっこ( )グループが含まれているが、これを抽出したくない場合がある。その場合、かっこは先頭に ?: を付けて記述します。たとえば、(?: )、その左かっこはグループ結果としてカウントされません)。
RE ワークフローとデバッグ
正規表現パターンは、わずか数文字で多くの意味が詰め込まれますが、非常に密集しているため、パターンのデバッグに多くの時間を費やすことになります。ランタイムをセットアップして、パターンを実行して一致するものを簡単に出力できるようにします。たとえば、小さなテストテキストで実行し、findall() の結果を出力できるようにします。一致するパターンがない場合は、パターンを弱めて、一致する部分を一部削除してみてください。一致するものがない場合、具体的な調査がないため、作業を進めることができません。一致しすぎたら、徐々に調整して目的のレベルにまで調整します。
オプション
re 関数は、パターン一致の動作を変更するオプションを受け取ります。オプション フラグは、search() や findall() などに引数として追加されます。たとえば、re.search(pat, str, re.IGNORECASE) を使用します。
- IGNORECASE -- 照合で大文字と小文字の違いを無視するので、「a」「a」と「a」の両方に一致あります。
- DOTALL -- ドット(.)を改行と一致させます。通常は、改行以外と一致します。「.*」はすべてに一致すると思いますが、デフォルトでは行の末尾以降ません。\s(空白文字)には改行が含まれているので、改行が含まれる可能性がある空白文字に一致する場合は、\s* を使用します。
- 複数行 -- 多数の行から成る文字列内で、^ と $ を各行の先頭と末尾に一致させることができます。通常、「^/$」は文字列全体の始まりと終わりに一致します。
「貪欲」と「非貪欲」(任意)
このセクションは省略可能で、演習には必要ない、より高度な正規表現の手法を示します。
たとえば、<b>foo</b> というタグの付いたテキストがあるとします。その他<i></i>
各タグをパターン「(<.*>)」と照合するとします。-- 最初に一致するものは何ですか?
結果は少し意外ですが、.* が欲張りな側面であるため、これは '<b>foo</b> 全体と一致します。その他<i></i>1 つの大きな一致と見なすことができます問題は、.* が最初の > で止まるのではなく、できる限り多くなることです。(別名「欲張り」)。
正規表現の拡張機能として ?「.*?」と入力します。「.+?」を「欲張りでない」に変更します。今はすぐにやめてしまいます。したがって、パターン「(<.*?>)」は「<b>」のみが返されます'</b>' と指定して、というように、各 <..>ペア設定されます。スタイルでは通常、* を使用します。その直後に「.*?」という具体的なマーカー(この場合は>)が続きます。強制的に延長されます
*?拡張子は Perl に由来しており、Perl の拡張機能を含む正規表現は、Perl 互換正規表現 -- pcre として知られています。Python には pcre のサポートが含まれます多くのコマンドライン ユーティリティなどには、pcre パターンを受け入れるフラグがあります。
「X で終わる文字以外」という考え方をコーディングするために、古くから広く使用されている手法です。角かっこスタイルを使用します。上記ではパターンを記述できますが、* の代わりにすべての文字を取得するには [^>]* を使用します。これにより、> 以外のすべての文字がスキップされます。(先頭の ^ は角かっこのセットを「反転」するため、角かっこ内にないすべての文字と一致します)。
置換(省略可)
re.sub(pat, replacement, str) 関数は、指定された文字列に含まれるパターンのすべてのインスタンスを検索して置換します。置換文字列には「\1」、「\2」を含めることができます。は、元の一致するテキストから group(1)、group(2) などから抽出されたテキストを参照します。
次の例では、すべてのメールアドレスを検索し、ユーザー(\1)を保持し、ホストを yo-yo-dyne.com とします。
str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher' ## re.sub(pat, replacement, str) -- returns new string with all replacements, ## \1 is group(1), \2 group(2) in the replacement print(re.sub(r'([\w\.-]+)@([\w\.-]+)', r'\1@yo-yo-dyne.com', str)) ## purple alice@yo-yo-dyne.com, blah monkey bob@yo-yo-dyne.com blah dishwasher
エクササイズ
正規表現を練習するには、赤ちゃんの名前のエクササイズをご覧ください。