現在地: ホーム Dive Into Python 3

難易度: ♦♦♦♢♢

正規表現

ある人々は問題に直面すると、「そうか、正規表現を使うんだ」と考える。こうして彼らは2つの問題を抱えることになる。
Jamie Zawinski

 

飛び込む

大きなテキストのかたまりから小さなテキストのかけらを取り出すのは挑戦的な課題だ。Pythonの文字列は、検索や置換のためのメソッドとしてindex(), find(), split(), count(), replace()などを持っている。しかし、これらのメソッドが機能するのは最も単純な場合に限られる。例えば、index()メソッドはハードコードされた1つの部分文字列を探すものだが、その検索は常に大文字と小文字が区別される。大文字と小文字を区別しないで検索したいときは、s.lower()またはs.upper()を呼び出して、さらに検索文字列の大文字と小文字もそれに合わせなければならない。replace()メソッドやsplit()メソッドも同様の制限を持っている。

文字列のメソッドで目的が達成できるのであればそれを使うべきだ。文字列のメソッドは高速でシンプルで読みやすく、それには数多くの利点がある。しかし、if文や様々な文字列関数を組み合わせて特殊な場合に対処している場合や、文字列を切り刻むためにsplit()join()を連鎖して呼びだしている場合は、正規表現へ移行する必要があるかもしれない。

正規表現はパワフルであり、複雑な文字パターンを使って文字列を検索、置換、パースするための(ほぼ)統一された方法だ。正規表現の構文はギチギチしていて普通のコードとは似つかないものだが、結果としては長々と文字列関数を連ねる自前の解法よりももっと読みやすいものになりうる。正規表現の中にコメントを埋め込む方法さえあるので、きめ細かいドキュメントを中に含めておくこともできる。

正規表現を他の言語(例えば Perl, JavaScript, PHP)で使ったことがあるのなら、Pythonでの構文もそれに非常に良く似たものだ。reモジュールの概要を読んで、利用可能な関数とそれらの引数についての概観を得てほしい。

ケーススタディ: 番地

この一連の例は、何年か前に私が仕事で出くわした実際の問題から着想を得ている。その時、私は古いシステムからエクスポートされた番地をきれいに標準化して新しいシステムへインポートしなければならなかった(ほら、だからこれは説明のために作り上げられた例なんかではなく、現実に役立つものなんだ)。この例は私がその問題にどのようにアプローチしたのかを示している。

>>> s = '100 NORTH MAIN ROAD'
>>> s.replace('ROAD', 'RD.')                
'100 NORTH MAIN RD.'
>>> s = '100 NORTH BROAD ROAD'
>>> s.replace('ROAD', 'RD.')                
'100 NORTH BRD. RD.'
>>> s[:-4] + s[-4:].replace('ROAD', 'RD.')  
'100 NORTH BROAD RD.'
>>> import re                               
>>> re.sub('ROAD$', 'RD.', s)               
'100 NORTH BROAD RD.'
  1. 私の目標は番地の標準化を行い、'ROAD'が常に省略形の'RD.'になるようにすることだ。一見したところ、私は文字列メソッドのreplace()を使えば十分だと思った。何にせよ、すべてのデータが既に大文字になっているので、大文字と小文字の違いは問題にならない。検索文字列は定数'ROAD'でよい。そして、この簡単そうな例では、s.replace()は実際に動作する。
  2. 人生は不幸にも反例に満ちており、私はすぐにこれを発見することになった。ここでの問題は'ROAD'が番地の中で2度現れることに起因している。1つは通りの名前'BROAD'の一部として現れ、もう1つは'ROAD'単独で現れる。replace()メソッドはこれら2つを見つけ出し、両方を機械的に置換してしまう。つまり、破壊された番地が得られてしまうのだ。
  3. 'ROAD'という部分文字列を複数含む番地の問題を解決するために、次のような解法に頼ることができる。番地の最後の4文字 (s[-4:]) にある'ROAD'についてのみ検索と置換を行い、その他の部分 (s[:-4]) はそのままにしておくのだ。しかし、やり方がすでに不格好になってきていることが分かるだろう。例えば、この方式は置換しようとしている文字列の長さに依存している(仮に'STREET''ST.'で置換したいとすると、s[:-6]s[-6:].replace(...)を使わなければならない)。6ヶ月後に戻ってきたときにこんなものをデバッグしたいだろうか? 私はいやだね。
  4. 正規表現へ移行する時がきたようだ。Pythonでは、正規表現に関するすべての機能はreモジュールに入っている。
  5. 1つ目のパラメータを見て欲しい。'ROAD$'だ。この単純な正規表現は、文字列の末尾に出現する'ROAD'だけにマッチする。$は「文字列の末尾」を意味するのだ(これと対称な文字として、キャレット文字^があり、これは「文字列の先頭」を意味する)。re.sub()を使ってsから正規表現'ROAD$'を探して、それを'RD.'で置換する。これは文字列sの末尾にあるROADにはマッチするが、BROADの一部として含まれるROADは文字列の中間にあるのでマッチしない

番地をきれいにする話を続けると、私はすぐに、前の例のように文字列の末尾にある'ROAD'にマッチするだけでは不十分であることを発見した。なぜなら、すべての番地に街路表示があるわけではないからだ。いくつかの番地は単に街路名で終わっている。これは多くの場合に無視できたのだが、街路名が'BROAD'の場合、この正規表現だと文字列の末尾にある'BROAD'のうちの'ROAD'にマッチしてしまう。これは私の望む結果ではない。

>>> s = '100 BROAD'
>>> re.sub('ROAD$', 'RD.', s)
'100 BRD.'
>>> re.sub('\\bROAD$', 'RD.', s)   
'100 BROAD'
>>> re.sub(r'\bROAD$', 'RD.', s)   
'100 BROAD'
>>> s = '100 BROAD ROAD APT. 3'
>>> re.sub(r'\bROAD$', 'RD.', s)   
'100 BROAD ROAD APT. 3'
>>> re.sub(r'\bROAD\b', 'RD.', s)  
'100 BROAD RD. APT 3'
  1. 私が本当に望んでいたことは、文字列の末尾にあり、なおかつ(他の単語の一部としてではなく)それ単独で出現する'ROAD'にマッチすることだ。これを正規表現で表現するには\bを使えばよい。この記号は「ここに単語境界がある」ことを意味する。Pythonでは文字列中の'\'という文字がエスケープされるので話が複雑になる。これはバックスラッシュの災いと呼ばれることもあり、PythonよりもPerlのほうが正規表現が使いやすい1つの理由でもある。反対の面では、Perlは正規表現を他の構文と混ぜ合わせているので、バグ存在するときに、構文にバグがあるのか正規表現にバグがあるのかを区別しにくいかもしれない。
  2. バックスラッシュの災いに対処するために、Raw文字列というものを使うことができる。これを使うには文字列の前にrという文字を付ければよい。これは「その文字列の中でエスケープしてはいけない」ということをPythonに伝えるもので、例えば、通常'\t'はタブ文字であるが、r'\t'は本当のバックスラッシュ\とそれに続くtになる。正規表現を使うときは常にRaw文字列を使うことをおすすめする。さもなければ、一瞬でわけが分からなくなってしまう(それでなくとも、正規表現自体がすでにややこしいのだ)。
  3. やれやれ。悲しいことに、すぐに私の論理に反するケースを見つけてしまった。このケースでは、'ROAD'は番地に1つの単語として含まれているにも関わらず、街路表記の後ろにアパートの番号があるために、文字列の末尾に現れない。そして'ROAD'が文字列の末尾にないので、正規表現はマッチせず、re.sub()を呼び出してもまったく何も置換しないで元の文字列をそのまま返してしまうのだ。
  4. これを解決するために、私は$文字を取り除いて、もう一つの\bを加えた。この正規表現はこう読める「文字列中のどこであっても、'ROAD'を1つの単語として含むものにマッチする」。先頭でも、末尾でも、途中のどこでも、だ。

ケーススタディ: ローマ数字

ローマ数字というものを、たとえ読むことはできなくても、見たことくらいはあるだろう。古い映画やテレビ番組の著作権表示で見たことがあるかもしれないし("Copyright 1946" の代わりに "Copyright MCMXLVI" になっている)、図書館や大学に貢献した人の名前を連ねた壁に書かれているのを見たことがあるかもしれないし("established 1888" の代わりに "established MDCCCLXXXVIII" になっている)、書誌参照の中で見たことがあるかもしれない。このローマ数字は、数を表現するための体系の1つで、実際に古代ローマ帝国時代に使われていたものだ(それゆえにローマ数字と呼ばれる)。

ローマ数字には、数を表現するために繰り返されたり組み合わせられたりする文字が7つある。

以下はローマ数字を構築するための一般的な規則である:

1000の位をチェックする

任意の文字列が正しいローマ数字であることを検証するにはどうしたらよいのだろうか? 1桁ごとに考えていこう。ローマ数字は常に大きい位から小さい位へと書かれるので、まずは最も大きな位である1000の位から始めていこう。1000以上の数では、1000の位はMという文字の並びで表される。

>>> import re
>>> pattern = '^M?M?M?$'        
>>> re.search(pattern, 'M')     
<_sre.SRE_Match object at 0106FB58>
>>> re.search(pattern, 'MM')    
<_sre.SRE_Match object at 0106C290>
>>> re.search(pattern, 'MMM')   
<_sre.SRE_Match object at 0106AA38>
>>> re.search(pattern, 'MMMM')  
>>> re.search(pattern, '')      
<_sre.SRE_Match object at 0106F4A8>
  1. このパターンは3つの部分からなる。^は文字列の先頭から始まるものにマッチする。もしこれがないと、パターンはMという文字がどこにあってもマッチしてしまう。これは望む結果ではない。Mという文字が存在するのであれば、それが文字列の先頭にあることを確認したい。M?は1つのMという文字に任意でマッチする(つまり、Mが0回または1回現れるものにマッチする)。それが3回繰り返されているので、0回から3回Mが連続しているところにマッチすることになる。そして$は文字列の末尾にマッチする。これが先頭にある^と同時に使われると、パターンが文字列全体にマッチしなければならなくなり、Mの前後に他の文字が入ったものにはマッチしなくなる。
  2. reモジュールの核心となるのがsearch()関数だ。この関数は正規表現(pattern)と、その正規表現とマッチさせるための文字列('M')を引数に取る。マッチがもし見つかれば、search()はマッチを表現するための様々なメソッドを持ったオブジェクトを返す。マッチが1つも見つからなければ、search()はPythonのNull値であるNoneを返す。現段階で関心があるのはパターンがマッチするかどうかだけであり、それはsearch()の戻り値を見るだけで判断できる。'M'はこの正規表現にマッチする。なぜなら1つ目の省略可能なMはマッチし、2つ目と3つ目の省略可能なMは無視されるからだ。
  3. 'MM'はマッチする。1つ目と2つ目の省略可能なMがマッチし、3つ目のMは無視されるからだ。
  4. 'MMM'はマッチする。3つすべてのMがマッチするからだ。
  5. 'MMMM'はマッチしない。3つすべてのMがマッチするが、この正規表現は更に文字列がそこで終わっていることを要求している($という文字があるからだ)。しかし文字列はまだ終わっていない(4番目のMがある)。したがってsearch()Noneを返す。
  6. 面白いことに、空の文字列もこの正規表現にマッチする。すべてのMは省略可能だからだ。

100の位をチェックする

100の位は1000の位よりも難しい。なぜなら、それぞれの値に応じていくつかの異なる表現方法が使われるからだ。

つまり、あり得るパターンは4つある:

最後の2つのパターンは1つにまとめることができる。

次の例はローマ数字の100の位をどうやって検証するのかを示している。

>>> import re
>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)$'  
>>> re.search(pattern, 'MCM')             
<_sre.SRE_Match object at 01070390>
>>> re.search(pattern, 'MD')              
<_sre.SRE_Match object at 01073A50>
>>> re.search(pattern, 'MMMCCC')          
<_sre.SRE_Match object at 010748A8>
>>> re.search(pattern, 'MCMC')            
>>> re.search(pattern, '')                
<_sre.SRE_Match object at 01071D98>
  1. このパターンは前の例と同じように始まっており、まず文字列の最初であることをチェックし (^)、次に1000の位をチェックする (M?M?M?)。ここからの括弧の中が新しい部分であり、同時に使われることのない3種類のパターン、すなわちCMCD、そしてD?C?C?C?(これは省略可能なDの後に、0個から3個のCが続いたもの)の3つが縦棒によって区切られて定義されている。正規表現のパーサはこれら3つそれぞれのパターンを(右から左へ)順番にチェックし、最初にマッチしたものを取り上げて、残りは無視する。
  2. 'MCM'はマッチする。なぜなら最初のMはマッチし、2番目と3番目のMは無視され、そしてCMがマッチするからだ(従ってCDD?C?C?C?のパターンは試されてすらいない)。MCM1900をローマ数字で表したものだ。
  3. 'MD'はマッチする。なぜなら最初のMはマッチし、2つ目と3つ目のMは無視され、D?C?C?C?のパターンはDにマッチするからだ(3つのCはそれぞれが省略可能なので無視される)。MD1500をローマ数字で表したものだ。
  4. 'MMMCCC'はマッチする。なぜなら3つのM全部がマッチし、D?C?C?C?パターンがCCCにマッチするからだ(Dは省略可能なので無視される)。MMMCCC3300をローマ数字で表したものだ。
  5. 'MCMC'はマッチしない。最初のMはマッチし、2つ目と3つ目のMは無視され、CMはマッチする。しかし、まだ文字列の末尾に到達していない(まだマッチしていないCがある)ので、$はマッチしない。CはパターンD?C?C?C?の一部としてはマッチしない。なぜならCMのパターンが既にマッチしているので、D?C?C?C?のパターンを用いる余地は無いからだ。
  6. 面白いことに、空の文字列は依然としてこのパターンにマッチする。なぜなら全てのMは省略可能なので無視され、しかも空文字列はパターンD?C?C?C?(これらの文字はどれも省略可能なので無視される)にマッチするからだ。

やーれやれ! 正規表現がたちどころに汚くなってしまうことが分かっただろうか? そして、これでもまだローマ数字の1000の位と100の位をカバーしただけなのだ。だが、ここまでくれば、後の10の位と1の位は簡単にできるだろう。というのも、これらはまったく同じようなパターンだからだ。しかし、次はパターンを表現する別の方法を見てみよう。

{n,m}構文を使う

前節では、同じ文字が最高で3回まで繰り返されるパターンを扱った。正規表現には、これを表現する別の方法が存在し、人によってはこちらの方が読みやすいだろう。まずは前の例で使ったやり方を見てみよう。

>>> import re
>>> pattern = '^M?M?M?$'
>>> re.search(pattern, 'M')     
<_sre.SRE_Match object at 0x008EE090>
>>> pattern = '^M?M?M?$'
>>> re.search(pattern, 'MM')    
<_sre.SRE_Match object at 0x008EEB48>
>>> pattern = '^M?M?M?$'
>>> re.search(pattern, 'MMM')   
<_sre.SRE_Match object at 0x008EE090>
>>> re.search(pattern, 'MMMM')  
>>> 
  1. これは文字列の先頭にマッチし、次に省略可能なMにマッチする。しかし、2番目と3番目のMにはマッチせず(しかしこれは省略可能なので問題ない)、そして文字列の末尾にマッチする。
  2. これは文字列の先頭にマッチし、次に1番目と2番目の省略可能なMにマッチする。しかし3番目のMにはマッチせず(しかしこれは省略可能なので問題ない)、そして文字列の末尾にマッチする。
  3. これは文字列の先頭にマッチし、次にすべての省略可能なMにマッチし、次に文字列の末尾にマッチする。
  4. これは文字列の先頭にマッチし、次にすべての省略可能なMにマッチする。しかし文字列の終わりにはマッチしないので(まだマッチしていないMがあるから)、パターンはマッチせずNoneが返る。
>>> pattern = '^M{0,3}$'        
>>> re.search(pattern, 'M')     
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MM')    
<_sre.SRE_Match object at 0x008EE090>
>>> re.search(pattern, 'MMM')   
<_sre.SRE_Match object at 0x008EEDA8>
>>> re.search(pattern, 'MMMM')  
>>> 
  1. このパターンは「文字列の先頭にマッチし、0個以上3個以下のMにマッチし、次に文字列の終わりにマッチする」と言っている。この0と3はどんな数値であっても構わない。もし1個以上3個以下のMにマッチさせたいのであれば、M{1,3}にすればよい。
  2. これは文字列の先頭にマッチし、次にありうる3つのうちの1つのMにマッチし、次に文字列の末尾にマッチする。
  3. これは文字列の先頭にマッチし、次にありうる3つのうちの2つのMにマッチし、次に文字列の末尾にマッチする。
  4. これは文字列の先頭にマッチし、次にありうる3つのうちの3つのMにマッチし、次に文字列の末尾にマッチする。
  5. これは文字列の先頭にマッチし、次にありうる3つのうちの3つのMにマッチする。しかし、文字列の末尾にはマッチしない。この正規表現は文字列の末尾の前に最高で3つまでのMを許容するが、ここでは4つあるので、パターンはマッチせずNoneが返る。

10の位と1の位をチェックする

それでは、ローマ数字の正規表現を拡張して10の位と1の位も扱えるようにしよう。次の例は10の位をチェックするものだ。

>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$'
>>> re.search(pattern, 'MCMXL')     
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCML')      
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCMLX')     
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCMLXXX')   
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCMLXXXX')  
>>> 
  1. これは文字列の先頭にマッチし、次に最初の省略可能なMにマッチし、次にCMにマッチし、次にXLにマッチし、次に文字列の末尾にマッチする。(A|B|C)構文が「AかBかCのどれか1つだけにマッチする」というものだったことを思いだそう。ここではXLにマッチしたので、XCL?X?X?X?の選択肢は無視されて、文字列の末尾へ進む。MCMXL1940をローマ数字で表したものだ。
  2. これは文字列の先頭にマッチし、次に最初の省略可能なMにマッチし、次にCMにマッチし、次にL?X?X?X?にマッチする。L?X?X?X?では、Lにマッチして選択的なXをすべてスキップする。そして文字列の末尾に到達する。MCML1950をローマ数字で表したものだ。
  3. これは文字列の先頭にマッチし、次に最初の省略可能なMにマッチし、次にCMにマッチし、次に省略可能なLにマッチし、次に1つ目の省略可能なXにマッチし、2つ目と3つ目の省略可能なXは無視され、次に文字列の末尾にマッチする。MCMLX1960をローマ数字で表したものだ。
  4. これは文字列の先頭にマッチし、次に最初の省略可能なMにマッチし、次にCMにマッチし、次に省略可能なLにマッチし、次に3つすべての省略可能なXにマッチし、次に文字列の末尾にマッチする。MCMLXXX1980をローマ数字で表したものだ。
  5. これは文字列の先頭にマッチし、次に最初の省略可能なMにマッチし、次にCMにマッチし、次に省略可能なLにマッチし、次に3つすべての省略可能なXにマッチするが、文字列の末尾にはマッチしない。なぜなら、まだマッチしていないXが残っているからだ。従ってパターン全体はマッチせず、Noneを返す。MCMLXXXは有効なローマ数字ではない。

1の位の表現もこれと同様だ。詳細は省いて結果だけを示そう。

>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$'

それでは、{n,m}構文を使った場合はどうなるのだろうか? 次の例ではこの新しい構文を使っている。

>>> pattern = '^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$'
>>> re.search(pattern, 'MDLV')              
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MMDCLXVI')          
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MMMDCCCLXXXVIII')   
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'I')                 
<_sre.SRE_Match object at 0x008EEB48>
  1. これは文字列の先頭にマッチし、3つあり得るうちの1つのMにマッチし、D?C{0,3}にマッチする。つまり、これは省略可能なDと3つあり得るうちの0個のCにマッチしている。続けて、省略可能なLと3つありうるうちの0個のXにマッチすることでL?X{0,3}にマッチする。次に、省略可能なVと3つあり得るうちの0個のIにマッチすることでV?I{0,3}にマッチする、そして、最後に文字列の末尾にマッチする。MDLV1555をローマ数字で表したものだ。
  2. これは文字列の先頭にマッチし、3つあり得るうちの2つのMにマッチし、D?C{0,3}Dと3つあり得るうちの1つのCでマッチし、L?X{0,3}Lと3つあり得るうちの1つのXでマッチし、V?I{0,3}Vと3つあり得るうちの1つのIでマッチし、文字列の末尾にマッチする。MMDCLXVI2666をローマ数字で表したものだ。
  3. これは文字列の先頭にマッチし、3つあり得るうちの2つのMにマッチし、D?C{0,3}Dと3つあり得るうちの3つのCでマッチし、L?X{0,3}Lと3つあり得るうちの3つのXでマッチし、V?I{0,3}Vと3つあり得るうちの3つのIでマッチし、文字列の末尾にマッチする。MMMDCCCLXXXVIII3888をローマ数字で表したものであり、これは拡張された構文を用いずに表すことができる最も長いローマ数字だ。
  4. じっと見て欲しい(私はマジシャンになった気分だ。「よく見て、坊や、帽子からウサギを取り出してみせるよ」)。これは文字列の先頭にマッチし、3個あり得るうちの0個のMにマッチし、次のD?C{0,3}には、省略可能なDを飛ばして3個あり得るうちの0個のCでマッチし、次のL?X{0,3}には、省略可能なLを飛ばして3個あり得るうちの0個のXでマッチし、次のV?I{0,3}には、省略可能なVを飛ばして3個あり得るうちの1個のIでマッチし、文字列の末尾にマッチする。おおー。

もし初挑戦でこれを全て理解できたのなら、私が初めて学んだときよりも上手くやっているよ。さて今度は、他の人が書いた正規表現を読む時のことを考えてみよう。それも、巨大なプログラムのとりわけ重要な関数の中ほどあたりにあるやつを。あるいは、自分自身で書いた正規表現を何ヶ月か後に見かえすときのことを想像してもいい。私の経験を言えば、とても読めたものではなかったね。

今度は、正規表現をより保守しやすくしてくれる構文について学ぼう。

冗長な正規表現

これまでは、私が「コンパクトな」正規表現と呼んでいるものを扱ってきた。以上の例を見て分かったように、このような正規表現は読み難く、たとえその内容を一旦は理解できたとしても、6ヶ月後にまた理解できる保証が無いような代物だった。ここで本当に必要なものはインラインのドキュメントだ。

Pythonでは冗長な正規表現と呼ばれるものを使ってこれを行うことができる。冗長な正規表現はコンパクトな正規表現とは2つの点で異なる:

これは実際の例を見ればよく分かる。もう一度いままでのコンパクトな正規表現を見てみよう。そして、それを冗長な正規表現にするのだ。この例はそのやり方を示している。

>>> pattern = '''
    ^                   # beginning of string
    M{0,3}              # thousands - 0 to 3 Ms
    (CM|CD|D?C{0,3})    # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 Cs),
                        #            or 500-800 (D, followed by 0 to 3 Cs)
    (XC|XL|L?X{0,3})    # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 Xs),
                        #        or 50-80 (L, followed by 0 to 3 Xs)
    (IX|IV|V?I{0,3})    # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 Is),
                        #        or 5-8 (V, followed by 0 to 3 Is)
    $                   # end of string
    '''
>>> re.search(pattern, 'M', re.VERBOSE)                 
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCMLXXXIX', re.VERBOSE)         
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MMMDCCCLXXXVIII', re.VERBOSE)   
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'M')                             
  1. 冗長な正規表現を使うときに思い出すべき最も重要なことは、これを使うには追加の引数re.VERBOSEを渡す必要があるということだ。re.VERBOSEreモジュールで定義される定数であり、この定数はパターンを冗長な正規表現として扱わなければならないことを知らせる。見ての通り、このパターンはたくさんの空白(全て無視される)と、いくつかのコメント(全て無視される)を含んでいる。空白やコメントを無視してしまえば、これは前の節で見た正規表現とまったく同じだが、ずっと読みやすいものになっている。
  2. これは文字列の先頭にマッチし、次に3つあり得るうちの1つのMにマッチし、次にCMにマッチし、次にLと、3つあり得るうちの3つのXにマッチし、次にIXにマッチし、次に文字列の末尾にマッチする。
  3. これは文字列の先頭にマッチし、次に3つあり得るうちの3つのMにマッチし、次にDと、3つあり得るうちの3つのCにマッチし、次にLと、3つあり得るうちの3つのXにマッチし、次にVと、3つあり得るうちの3つのIにマッチし、次に文字列の末尾にマッチする。
  4. これはマッチしない。なぜだろうか? なぜならre.VERBOSEフラグが与えられていないからだ。従って、re.search関数はこのパターンをコンパクトな正規表現として扱い、空白を無視せず、#も文字通りのものと解釈する。Pythonは冗長な正規表現であるかどうかを自動で判定してはくれない。明示的に冗長な正規表現だと宣言しない限り、Pythonはすべての正規表現をコンパクトな正規表現とみなすのだ。

ケーススタディ: 電話番号をパースする

これまでは、パターン全体がマッチするものばかりを扱ってきた。パターンはマッチするかしないかのどちらかしかなかった。しかし、正規表現はそれよりももっと強力なものだ。正規表現がマッチするときは、その特定の一部分を取り出すことができる。どこで何がマッチしたのかを知ることができるのだ。

この例は、私が現実に出会った問題から着想を得ている。これまた私の前の仕事からのものだ。問題となったのはアメリカの電話番号のパースだった。その時、顧客が求めてきたのは、電話番号を自由な形式で(1つのフィールドに)入力できるようにしつつ、そこから市外局番・局番・残りの番号、そしてオプションとして内線番号を取り出して会社のデータベースに別々に格納することだった。私はWeb上を探し回り、この処理を行うと主張している正規表現の例をたくさん見つけたが、ライセンス的に使えるものが1つもなかった。

上手く処理できるようにしなければならなかった電話番号は次のようなものだ:

パターンが多すぎる! いずれの場合においても、市外局番は800で、局番は555で、残りの電話番号は1212であることを読み取れる必要がある。内線番号が存在する場合は、内線番号が1234であることも読み取れなくてはならない。

それでは電話番号をパースする方法を構築しよう。この例はその最初の一歩だ。

>>> phonePattern = re.compile(r'^(\d{3})-(\d{3})-(\d{4})$')  
>>> phonePattern.search('800-555-1212').groups()             
('800', '555', '1212')
>>> phonePattern.search('800-555-1212-1234')                 
>>> phonePattern.search('800-555-1212-1234').groups()        
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'groups'
  1. 正規表現は常に左から右へ読もう。これは文字列の先頭にマッチして、次に(\d{3})にマッチする。\d{3}というのは何だろう? \dは任意の数字(0から9まで)を意味している。{3}は「ちょうど3つの数字にマッチする」という意味で、前に見た{n,m}構文の一種だ。これを括弧の中に入れているのは「ちょうど3桁の数字にマッチさせ、マッチしたものを後で参照できるようにグループとして覚えておいてくれ」という意味だ。次にハイフンにマッチする。その次はまた別のちょうど3桁の数字のグループにマッチする。次にハイフンにマッチする。その次はまた別のちょうど4桁の数字にマッチする。次に文字列の終わりにマッチする。
  2. 正規表現のパーサが途中で記憶したグループにアクセスするには、search()メソッドが返したオブジェクトにあるgroups()メソッドを使う。このメソッドは正規表現で定義したグループがいくつあったとしても、それらをタプルで返してくれる。この場合は、3つのグループを定義している。そのうちの1つは3桁の数字で、もう1つは3桁の数字、最後の1つは4桁の数字だ。
  3. この正規表現で完成というわけではない。なぜなら末尾の内線番号を処理できるようになっていないからだ。内線番号を扱えるようにするには、この正規表現を拡張していく必要がある。
  4. これはsearch()メソッドとgroups()メソッドを製品コードで「連鎖」させてはいけない理由を表している。正規表現がマッチしなかった場合には、search()メソッドは正規表現のマッチオブジェクトではなく、Noneを返す。None.groups()を呼び出そうとすると、分かりきったことだが、例外が送出されることになる。Nonegroups()というメソッドを持っていないのだ(もちろん、コードの深いところでこの例外が出た場合は少し分かりづらくなる。そう、ここでは私は自分の経験に基づいてお話している)。
>>> phonePattern = re.compile(r'^(\d{3})-(\d{3})-(\d{4})-(\d+)$')  
>>> phonePattern.search('800-555-1212-1234').groups()              
('800', '555', '1212', '1234')
>>> phonePattern.search('800 555 1212 1234')                       
>>> 
>>> phonePattern.search('800-555-1212')                            
>>> 
  1. この正規表現は前のものとほとんど同じだ。先ほどと同様に、文字列の先頭にマッチし、次に記憶される3桁の数字のグループにマッチし、次にハイフンにマッチし、次に記憶される3桁の数字のグループにマッチし、次にハイフンにマッチし、次に記憶される4桁の数字のグループにマッチする。変更点は、ここからハイフンにマッチし、記憶される1桁以上の数字のグループにマッチし、文字列の末尾にマッチすることだ。
  2. 今度のgroups()メソッドは4つの要素からなるタプルを返す。正規表現に4つの記憶するグループを定義しているからだ。
  3. 残念だが、この正規表現も最終的な答えではない。なぜならこれは電話番号の個々の部分がハイフンで分割されていることを想定しているからだ。これがもしスペースやカンマやドットで区切られている場合にはどうなるだろうか? いくつもの異なる区切り文字に対応できるもっと一般化した解決策が必要だ。
  4. おっと! この正規表現は完璧な答えでないどころか、先ほどのものから後退してしまっている。というのは、これは内線番号のない電話番号をパースできないのだ。これでは全然お話にならない。欲しいのは、内線番号があればそれを読み取るが、仮に内線番号がなくとも、他の主番号の個々の部分について読み取ってくれるものだ。

次の例は、正規表現の個々の部分のあいだにある区切り文字を扱う正規表現を示している。

>>> phonePattern = re.compile(r'^(\d{3})\D+(\d{3})\D+(\d{4})\D+(\d+)$')  
>>> phonePattern.search('800 555 1212 1234').groups()  
('800', '555', '1212', '1234')
>>> phonePattern.search('800-555-1212-1234').groups()  
('800', '555', '1212', '1234')
>>> phonePattern.search('80055512121234')              
>>> 
>>> phonePattern.search('800-555-1212')                
>>> 
  1. しっかりとつかまっててくれ。文字列の先頭にマッチして、次に3桁の数字のグループにマッチして、次に\D+にマッチしている。これは一体何だろう? \Dは数字を除く全ての文字にマッチし、そして+は「1回以上」を意味している。つまり\D+は1つ以上の数字ではない文字にマッチする。異なる区切り文字にもマッチできるように、ハイフンの代わりにこれを使っているのだ。
  2. -の代わりに\D+を使うということは、ハイフンではなくスペースで分割された電話番号にもマッチすることを意味している。
  3. もちろん、ハイフンで分割された電話番号でも依然として機能する。
  4. 残念だが、これもまだ最終的な答えにはなっていない。なぜならこれは区切り文字が存在することを想定しているからだ。電話番号が区切り文字なしに入力された場合はどうなるだろうか?
  5. おっと! 内線番号が無ければ読み取れないという問題もまだ修正されていない。今のところ2つの問題を抱えていることになるが、両者は同じテクニックを使って解決できる。

次の例は区切り文字のない電話番号を扱う正規表現を示している。

>>> phonePattern = re.compile(r'^(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$')  
>>> phonePattern.search('80055512121234').groups()      
('800', '555', '1212', '1234')
>>> phonePattern.search('800.555.1212 x1234').groups()  
('800', '555', '1212', '1234')
>>> phonePattern.search('800-555-1212').groups()        
('800', '555', '1212', '')
>>> phonePattern.search('(800)5551212 x1234')           
>>> 
  1. 前のステップからの唯一の変更点は、+*に変更したことだけだ。\D+を電話番号の各部分の区切りとして使う代わりに、今度は\D*にマッチさせるのだ。+は「1回以上」を意味することを覚えているだろうか? *は「0回以上」を意味しているのだ。だから、たとえ区切り文字がまったく使われていない電話番号であったとしても、これでパースできるようになっているはずだ。
  2. 驚くなかれ、これで現に読み取れるのだ。なぜだろう? 文字列の先頭にマッチして、次に記憶しておく3桁の数字 (800) のグループにマッチし、次に0個の数字ではない文字にマッチし、次に記憶しておく3桁の数字 (555) のグループにマッチし、次に0個の数字ではない文字にマッチし、次に記憶しておく4桁の数字 (1212) のグループにマッチし、次に0個の数字ではない文字にマッチし、次に記憶しておく任意桁の数字 (1234) にマッチし、次に文字列の末尾にマッチする。
  3. 他のパターンも扱える: ハイフンの代わりにドットを使ったものや、内線番号の前にスペースとxがあるものでも対応できる。
  4. 長らく残っていた問題もついに解決できた: 内線番号は再び省略可能になったのだ。内線番号が見つかれば、groups()メソッドは4つの要素をもったタプルを返してくれるし、見つからなければ4番目の要素は単に空の文字列になる。
  5. 悪い知らせを伝えるのは嫌なのだが、実はまだ終わりではないのだ。ここでの問題は何か? 市外局番の前に余計な文字があるが、正規表現は文字列が市外局番から始まることを想定しているのだ。しかし問題はない。「0個以上の数字でない数」のテクニックを使うことで、市外局番の前にある文字を読み飛ばすことができる。

次の例は電話番号の前にある文字を扱い方を示している。

>>> phonePattern = re.compile(r'^\D*(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$')  
>>> phonePattern.search('(800)5551212 ext. 1234').groups()                  
('800', '555', '1212', '1234')
>>> phonePattern.search('800-555-1212').groups()                            
('800', '555', '1212', '')
>>> phonePattern.search('work 1-(800) 555.1212 #1234')                      
>>> 
  1. これは基本的に前の例と同じだが、最初に記憶されるグループ(市外局番)の前で、\D*(0個以上の数字ではない文字)にマッチさせている点が異なる。これらの数字でない文字は記憶されないことに注意しよう。もしそれらが見つかっても単に読み飛ばされてしまう。そして市外局番に行き着くと、それ以降の数字が記憶され始めるのだ。
  2. たとえ市外局番の前に左括弧があったとしても、上手くパースできる。(市外局番の後にある右括弧には既に対応済みだ。これは、最初に記憶されるグループの後ろにある数字ではない区切り文字として扱われ、\D*にマッチする)
  3. これは、今まで動作していたものを壊していないかを確認するための、単なる動作確認だ。前に来る文字は完全に省略可能なので、これは文字列の先頭にマッチし、次に記憶される3桁の数字のグループ(800)にマッチし、次に1つの数字ではない文字にマッチし(ハイフン)、次に記憶される3桁の数字のグループ(555)にマッチし、次に1つの数字ではない文字にマッチし(ハイフン)、次に記憶される4桁の数字のグループ(1212)にマッチし、次に0個の数字ではない文字にマッチし、次に記憶される0桁の数字のグループにマッチし、次に文字列の末尾にマッチする。
  4. 正規表現を使っていてこういう状況に陥ると、自分の目を鈍器でえぐり出したくなるね。なぜこの電話番号にはマッチしないのだろうか? なぜなら市外局番の前に1があるが、市外局番の前に来るものは全て数字でない文字(\D*)だと想定していたからだ。あーもう!

少し落ち着こう。今までの正規表現は文字列の頭から全部マッチさせてきた。しかし、現在では文字列の先頭に無視したいものがいくつも存在しうるということが分かっている。全体をマッチさせてそれらを読み飛ばすのではなく、違うやり方をしてみよう。つまり、明示的に文字列の先頭にはマッチさせないようにするのだ。このやり方を次の例で示す。

>>> phonePattern = re.compile(r'(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$')  
>>> phonePattern.search('work 1-(800) 555.1212 #1234').groups()         
('800', '555', '1212', '1234')
>>> phonePattern.search('800-555-1212')                                 
('800', '555', '1212', '')
>>> phonePattern.search('80055512121234')                               
('800', '555', '1212', '1234')
  1. この正規表現には^が無いことに注意しよう。もう文字列の先頭にはマッチさせないのだ。正規表現を入力全体とマッチさせる必要はどこにもない。正規表現エンジンが入力文字列にマッチし始める位置を頑張って見つけ出し、そこからマッチを行ってくれるのだ。
  2. 今度は、先頭に文字と数字を含んでいる電話番号もパースできるようになった。さらに、電話番号の各部分の周りに、どんな種類の区切り文字がどれだけ使われていてもパースできるようになっている。
  3. 動作確認。ちゃんと処理できている。
  4. これも処理できている。

正規表現が瞬く間に手に負えないものになってしまうことが分かっただろうか? 今までに繰り返してきたものをどれでも良いので少し見て欲しい。ある正規表現とその次のバージョンの正規表現を区別できるだろうか?

あなたがまだ最終バージョンの正規表現を理解しているうちに(これが最終的な答えだ。もしこれで扱えない例が見つかったとしても、私はそんなことは知りたくないね)、どうしてこのように構成したのかを忘れないうちに冗長な正規表現として書いておこう。

>>> phonePattern = re.compile(r'''
                # don't match beginning of string, number can start anywhere
    (\d{3})     # area code is 3 digits (e.g. '800')
    \D*         # optional separator is any number of non-digits
    (\d{3})     # trunk is 3 digits (e.g. '555')
    \D*         # optional separator
    (\d{4})     # rest of number is 4 digits (e.g. '1212')
    \D*         # optional separator
    (\d*)       # extension is optional and can be any number of digits
    $           # end of string
    ''', re.VERBOSE)
>>> phonePattern.search('work 1-(800) 555.1212 #1234').groups()  
('800', '555', '1212', '1234')
>>> phonePattern.search('800-555-1212')                          
('800', '555', '1212', '')
  1. 複数の行に展開されていることを除けば、最後のバージョンの正規表現とまったく同じだ。だから、この正規表現が同じ入力をパースできるのは当たり前のことだ。
  2. 最後の動作確認だ。よし、まだ動作する。これで終わりだ。

まとめ

今までの例は、正規表現ができることの氷山の一角にすぎない。言い換えれば、たとえ今あなたがこれらに完全に圧倒されているとしても、信じて欲しいが、こんなものはまだ序の口なのだ。

今あなたは、以下のテクニックを身につけているはずだ:

正規表現は非常に強力だが、すべての問題に対する正しい答えではない。正規表現がどんなときに適切で、どんなときに問題を解決してくれ、どんなときに初めの問題よりも多くの問題を引き起こしてしまうのかを、十分に学ぶ必要がある。

© 2001– Mark Pilgrim
© Fukada, Fujimoto(日本語版)