正規表現は、テキストパターンをマッチングするための強力な言語である。 このページでは、Pythonの練習に十分な正規表現の基本的な紹介と、正規表現がPythonでどのように動作するかを示します。 Pythonの “re “モジュールは正規表現のサポートを提供します。

Pythonの正規表現検索は通常次のように書かれます。

 match = re.search(pat, str)

re.search()メソッドは正規表現パターンと文字列を受け取り、文字列の中からそのパターンを検索します。 検索に成功した場合、search() は match オブジェクトを返し、そうでない場合は None を返します。 この例では、’word:’の後に3文字の単語が続くパターンを検索しています(詳細は後述):

str = 'an example word:cat!!'match = re.search(r'word:\w\w\w', str)# If-statement after search() tests if it succeededif match: print 'found', match.group() ## 'found word:cat'else: print 'did not find'

コード match = re.search(pat, str) は検索結果を “match” という変数に保存しています。 もしtrueなら、検索は成功し、match.group()はマッチしたテキスト(例えば’word:cat’)になる。 そうでなければ、match が false (より具体的には None) であれば、検索は成功せず、マッチするテキストはありません。

パターン文字列の最初にある ‘r’ は、バックスラッシュをそのまま通過する python “raw” 文字列を指定しており、正規表現では非常に便利です (Java はこの機能をひどく必要としています!)。

Basic Patterns

正規表現の力は、固定文字だけでなく、パターンを指定できることです。 以下は、単一の文字にマッチする最も基本的なパターンである:

  • a, X, 9, < — 通常の文字は、それ自身にマッチするだけである。 特別な意味を持つためにマッチしないメタキャラクタは次の通りである。 . ^ $ * + ? { \ | ( ) (詳細は後述)
  • . (ピリオド) — 改行以外の任意の1文字にマッチします ‘\n’
  • \w — (小文字のw) 「単語」文字: 文字または数字、アンダーバー にマッチします。 単語 “はこのためのニーモニックであるが、これは単一の単語文字にのみマッチし、単語全体にはマッチしないことに注意されたい。 \単語以外の文字には,W(大文字のW)がマッチします。
  • \b — 単語と非単語の境界
  • \s — (小文字のs) 空白文字1文字にマッチします — space, newline, return, tab, form . \S (upper case S) は任意の非空白文字にマッチします。
  • \t, \n, \r — tab, newline, return
  • \d — decimal digit (some older regex utilities do not support but \d, but they all support \w and \s)
  • ^ = start, $ = end — match the start or end of the string
  • \ — inhibit the “specialness of a character.” is inhibition. 例えば、ピリオドにマッチさせる場合は \.を、スラッシュにマッチさせる場合は \.を使用します。

Basic Examples

ジョーク:目が3つある豚を何と呼ぶか?

文字列内のパターンに対する正規表現検索の基本ルールは次のとおりです:

  • 検索は文字列を最初から最後まで進み、最初に見つかったマッチで停止します
  • パターンのすべてとマッチしなければならず、文字列のすべてではない場合、match = re.search(pat, str) が成功した場合 match は None でなく特に match.B を使用します。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"

Repetition

パターンの繰り返しを指定するのに + と * を使うともっと面白くなる

  • + — 左側に1回以上出現したパターン、例えば ‘i+’ = 1つまたは複数の i
  • * — 左側に0回以上のパターン出現
  • ?

Leftmost & Largest

最初にパターンに最も左にあるものを見つけ、次に文字列をできるだけ多く使おうとする。 + と*はできるだけ遠くまで行きます(+と*は「貪欲」だと言われています)。

繰り返しの例

 ## 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 [email protected] purple monkey」という文字列にある電子メールアドレスを探したいとしましょう。 これを実行例として、さらに正規表現の機能を説明する。 ここでは、パターンr’˶’˶’:

 str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'

この場合、メールアドレスの’-‘や’.’には一致しないので、検索しても全部は出てきませんね。

Square Brackets

角括弧は文字の集合を表すので、’a’ や ‘b’ や ‘c’ にマッチします。 ただし、ドット(.)はドットそのものを意味します。 メールの問題では、角かっこは、@の周りに現れる文字に’.’と’-‘を加える簡単な方法で、r’+@+’というパターンでメールアドレス全体を得ることができます:

 match = re.search(r'+@+', str) if match: print match.group() ## '[email protected]'

(角かっこの他の機能)ダッシュを使って範囲を示すこともでき、すべての小文字にマッチします。 範囲を指定せずにダッシュを使う場合は、ダッシュを最後に置きます(例:.

グループ抽出

正規表現の「グループ」機能は、マッチしたテキストの一部を抽出することができる。 メールの問題で、ユーザー名とホストを別々に抽出したいとします。 そのためには、r'(+)@(+)’のように、パターン中のユーザー名とホストを括弧( )で囲みます。 この場合、括弧はパターンがマッチする対象を変えるのではなく、マッチテキストの中に論理的な「グループ」を確立するものである。 検索に成功すると、match.group(1)は最初の左括弧に対応するマッチテキストとなり、match.group(2)は2番目の左括弧に対応するテキストとなります。

 str = 'purple [email protected] monkey dishwasher' match = re.search(r'(+)@(+)', str) if match: print match.group() ## '[email protected]' (the whole match) print match.group(1) ## 'alice-b' (the username, group 1) print match.group(2) ## 'google.com' (the host, group 2)

正規表現でよくあるワークフローは、探しているもののパターンを書き、必要な部分を抽出するために括弧グループを追加することです。

findall

findall() はおそらくreモジュールで唯一最も強力な関数でしょう。 findall() はマッチした *all* を見つけ、それを文字列のリストとして返し、 それぞれの文字列はマッチした 1 つを表します。

 ## Suppose we have a text with many email addresses str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## Here re.findall() returns a list of all the found email strings emails = re.findall(r'+@+', str) ## for email in emails: # do something with each found email string print email

findall With Files

ファイルについては、ファイルの行を反復するループを書いて、 各行で findall() を呼ぶ習慣があるかもしれません。 その代わり、findall() があなたのために反復処理を行います。 ファイル全体のテキストを findall() に送り、一度にすべてのマッチのリストを返させます (f.read() がファイルのテキスト全体を一つの文字列で返すことを思い出してください):

 # Open file f = open('test.txt', 'r') # Feed the file text into findall(); it returns a list of all the found strings strings = re.findall(r'some pattern', f.read())

findall and Groups

カッコ ( ) によるグループのメカニズムは findall() と一緒にすることができます。 もしパターンが2つ以上の括弧グループを含んでいれば、 findall() は文字列のリストを返す代わりに *tuples* のリストを返します。 各タプルはパターンの1つのマッチを表し、タプルの内部には group(1), group(2) … データがあります。 例えば、(‘alice’, ‘google.com’).

 str = 'purple [email protected], blah monkey [email protected] blah dishwasher' tuples = re.findall(r'(+)@(+)', str) print tuples ## for tuple in tuples: print tuple ## username print tuple ## host

一旦タプルのリストを得ると、それをループして各タプルに対して何らかの計算を行うことができます。 もしパターンが括弧を含んでいなければ、findall()は以前の例と同様に見つかった文字列のリストを返します。 パターンがひとつの括弧を含む場合、 findall() はそのひとつの括弧に対応する文字列のリストを返します。 (不明瞭なオプション機能。 パターン中に括弧( )のグループがあるが、それを抽出したくない場合がある。 そのような場合、たとえば (?: ) のように、先頭に ?: を付けて書くと、その左側の括弧はグループの結果としてカウントされません)

RE ワークフローとデバッグ

正規表現パターンはわずか数文字に多くの意味を詰め込んでいますが、非常に密なので、パターンのデバッグに多くの時間を費やしてしまうことがあります。 例えば、小さなテストテキストでパターンを実行し、findall() の結果を表示することで、パターンを実行し、マッチしたものを簡単に表示できるようにランタイムをセットアップしてください。 パターンが何もマッチしない場合は、パターンを弱くしてその一部を削除し、マッチが多くなるようにします。 何もマッチしないときは、見るべき具体的なものがないため、何も進展することはありません。

Options

re 関数は、パターンマッチの動作を変更するためのオプションを受け取ります。 オプションフラグは、例えば re.search(pat, str, re.IGNORECASE) のように search() や findall() などの追加引数として追加されます。

  • IGNORECASE — 大文字/小文字の違いを無視してマッチするので ‘a’ は ‘a’ にも ‘A’ にもマッチします。
  • DOTALL — ドット (.) を改行にマッチさせる — 通常は改行以外をマッチさせる。 .* は全てにマッチすると思っているが、デフォルトでは行末を越えることはない。 また、改行を含む空白をマッチさせたい場合は、 \s*
  • MULTILINE — 多くの行からなる文字列の中で、各行の先頭と末尾に ^ と $ をマッチさせることができるようにします。 通常 ^/$ は文字列全体の開始と終了にマッチします。

Greedy vs. Non-Greedy (optional)

This is optional section which shows a more advanced regular expression technique not needed for the exercises.

タグ付きのテキストがあるとします。 <b>foo</b> と <i>so on</i>

それぞれのタグをパターン「(<.*>)」でマッチさせようとしているとして、最初に何にマッチするか?

結果は少し意外ですが、.* の貪欲な側面により、「<b>foo</b> と <i>so on</i>」全体を一つの大きなマッチとしてマッチさせています。 問題は、.* が最初の > で停止せず、できる限り遠くまで行くことです (つまり、「欲張り」です)。

正規表現の拡張として、.* や .+ など、最後に ? を追加して、欲張らないように変更するものがあります。 これで、できる限り早く停止するようになった。 つまり、'(<.*?>)’ というパターンは、最初のマッチが ‘<b>’ だけで、2番目のマッチが ‘</b>’ となり、以下 <..> と順番に取得することになる。 典型的なスタイルは、.*? を使い、そのすぐ右側で .*? の実行を強制的に終了させる具体的なマーカー (この場合 >) を探すことです。

*? 拡張は Perl で生まれ、Perl の拡張を含む正規表現は Perl Compatible Regular Expressions — pcre として知られています。 Python は pcre のサポートを含んでいます。 多くのコマンドライン・ユーティリティなどは、pcreパターンを受け入れるフラグを持っています。

この「X で止まる以外のすべての文字」という考えをコード化する、古いが広く使われているテクニックは、角括弧スタイルを使用します。 上記のようにパターンを書くことができますが、すべての文字を取得する .* の代わりに、> 以外のすべての文字をスキップする * を使います (最初の ^ は角括弧のセットを「反転」するので、括弧内にないすべての文字にマッチします)。 置換文字列には、マッチング元のテキストからグループ(1)、グループ(2)、…のテキストを参照する’˶’s1’、’˶’2’を含めることができる。

ここでは、メールアドレスをすべて検索し、ユーザー(˶‾᷄ -̫ ‾᷅˵)はそのままに、ホストが yo-yo-dyne.com であるように変更する例を示します。

 str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## re.sub(pat, replacement, str) -- returns new string with all replacements, ##  is group(1),  group(2) in the replacement print re.sub(r'(+)@(+)', r'@yo-yo-dyne.com', str) ## purple [email protected], blah monkey [email protected] blah dishwasher

演習

正規表現の練習については、赤ちゃんの名前の練習をご覧ください。

admin

コメントを残す

メールアドレスが公開されることはありません。

lg