5章 正規表現

飛び込む

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

ケーススタディ: 番地

古いシステムからエクスポートされた番地をきれいに標準化して新しいシステムへインポートする例。

目標は番地の標準化を行い、'ROAD'が常に省略形の'RD.'になるようにすること。

検索文字列は定数'ROAD'でよい。そして、この簡単そうな例では、s.replace()は実際に動作する。

In [1]:
s = '100 NORTH MAIN ROAD'
s.replace('ROAD', 'RD.')
Out[1]:
'100 NORTH MAIN RD.'

こでの問題は'ROAD'が番地の中で2度現れることに起因している。 1つは通りの名前'BROAD'の一部として現れ、もう1つは'ROAD'単独で現れる。replace()メソッドはこれら2つを見つけ出し、両方を機械的に置換してしまう。 つまり、破壊された番地が得られてしまう。

In [2]:
s = '100 NORTH BROAD ROAD'
s.replace('ROAD', 'RD.')  
Out[2]:
'100 NORTH BRD. RD.'

'ROAD'という部分文字列を複数含む番地の問題を解決するために、次のような解法に頼ることができる。 番地の最後の4文字 (s[-4:]) にある'ROAD'についてのみ検索と置換を行い、その他の部分 (s[:-4]) はそのままにしておく。

例えば、この方式は置換しようとしている文字列の長さに依存している(仮に'STREET'を'ST.'で置換したいとすると、s[:-6]とs[-6:].replace(...)を使わなければならない)。

In [3]:
s[:-4] + s[-4:].replace('ROAD', 'RD.')
Out[3]:
'100 NORTH BROAD RD.'

Pythonでは、正規表現に関するすべての機能はreモジュールに入っている。

In [4]:
import re

1つ目のパラメータは、 'ROAD$'。 この単純な正規表現は、文字列の末尾に出現する'ROAD'だけにマッチする。 $は「文字列の末尾」を意味する (これと対称な文字として、キャレット文字^があり、これは「文字列の先頭」を意味する)。

re.sub()を使ってsから正規表現'ROAD$'を探して、それを'RD.'で置換する。 これは文字列sの末尾にあるROADにはマッチするが、BROADの一部として含まれるROADは文字列の中間にあるのでマッチしない。

In [5]:
re.sub('ROAD$', 'RD.', s) 
Out[5]:
'100 NORTH BROAD RD.'

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

In [6]:
s = '100 BROAD'
re.sub('ROAD$', 'RD.', s)
Out[6]:
'100 BRD.'

本当に望んでいたことは、文字列の末尾にあり、なおかつ(他の単語の一部としてではなく)それ単独で出現する'ROAD'にマッチすること。 これを正規表現で表現するには \b を使えばよい。 この記号は「ここに単語境界がある」ことを意味する。

Pythonでは文字列中の'\'という文字がエスケープされるので話が複雑になる。これはバックスラッシュの災いと呼ばれることもあり、PythonよりもPerlのほうが正規表現が使いやすい1つの理由でもある。

In [7]:
re.sub('\\bROAD$', 'RD.', s)
Out[7]:
'100 BROAD'

バックスラッシュの災いに対処するために、Raw文字列というものを使うことができる。 これを使うには文字列の前に r という文字を付ければよい。 これは「その文字列の中でエスケープしてはいけない」ということをPythonに伝えるもの。

例えば、通常'\t'はタブ文字であるが、r'\t'は本当のバックスラッシュ\とそれに続くtになる。 正規表現を使うときは常にRaw文字列を使うことをおすすめ。

In [8]:
re.sub(r'\bROAD$', 'RD.', s)
Out[8]:
'100 BROAD'

このケースでは、'ROAD'は番地に1つの単語として含まれているにも関わらず、街路表記の後ろにアパートの番号があるために、文字列の末尾に現れない。そして'ROAD'が文字列の末尾にないので、正規表現はマッチせず、re.sub()を呼び出してもまったく何も置換しないで元の文字列をそのまま返してしまう。

In [9]:
s = '100 BROAD ROAD APT. 3'
re.sub(r'\bROAD$', 'RD.', s)
Out[9]:
'100 BROAD ROAD APT. 3'

$文字を取り除いて、もう一つの\bを加えた。 この正規表現はこう読める「文字列中のどこであっても、'ROAD'を1つの単語として含むものにマッチする」。先頭でも、末尾でも、途中のどこでも。

In [10]:
re.sub(r'\bROAD\b', 'RD.', s)
Out[10]:
'100 BROAD RD. APT. 3'

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

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

  • I = 1
  • V = 5
  • X = 10
  • L = 50
  • C = 100
  • D = 500
  • M = 1000

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

  • 時に文字は足し算のように働く。

    • Iは1
    • IIは2
    • IIIは3
    • VIは6で(文字通り「5と1」だ)
    • VIIは7
    • VIIIは8
  • 10の文字(I:1、X:10、C:100、M:1000)は三回まで繰り返せる。

    • 4については、次の5の文字から引いて表さなければならない。つまり、4をIIIIと表すことはできず、代わりにIVとしなければならない(「5引く1」)。
    • 40はXLと書かれ(「50引く10」)
    • 41はXLI
    • 42はXLII
    • 43はXLIII
    • そして44はXLIVと表せられる(「50引く10と5引く1」)。
  • 時に文字は……足し算とは逆の役割を果たす。ある文字を他の文字の前に置くと、後の文字から値を引いたことになるのだ。 例えば、9を作るには、次の10の数から引き算をしなければならない。つまり、8はVIIIだが、

    • 9はIXとなり(「10引く1」)、VIIIIとは書けないのだ(なぜならIの文字を4回繰り返すことはできないから)。
    • 90はXC
    • 900はCM。
  • 5の文字は繰り返すことができない。10は必ずXと表し、VVとすることはできない。100もCであって、LLとはならない。 ローマ数字は左から右に読むので、文字の並べ方が非常に重要になる。

    • DCは600
    • CDはそれとは全く異なる数を表すのだ(400、「500引く100」)。
    • CIは101だがICは適切なローマ数字ですらない(1を直接100から引くことはできない。 代わりにXCIX(99)と書かなくてはならない、「100引く10、加えて10引く1」)。

1000の位をチェックする

ローマ数字は常に大きい位から小さい位へと書かれるので、まずは最も大きな位である1000の位から始める。 1000以上の数では、1000の位はMという文字の並びで表される。

このパターンは3つの部分からなる。

  • ^は文字列の先頭から始まるものにマッチする。 もしこれがないと、パターンはMという文字がどこにあってもマッチしてしまう。これは望む結果ではない。Mという文字が存在するのであれば、それが文字列の先頭にあることを確認したい。
  • M?は1つのMという文字に任意でマッチする(つまり、Mが0回または1回現れるものにマッチする)。それが3回繰り返されているので、0回から3回Mが連続しているところにマッチすることになる。
  • $は文字列の末尾にマッチする。 これが先頭にある^と同時に使われると、パターンが文字列全体にマッチしなければならなくなり、Mの前後に他の文字が入ったものにはマッチしなくなる。
In [11]:
import re
pattern = '^M?M?M?$' 

reモジュールの核心となるのがsearch()関数。 この関数は正規表現(pattern)と、その正規表現とマッチさせるための文字列('M')を引数に取る。

  • マッチがもし見つかれば、search()はマッチを表現するための様々なメソッドを持ったオブジェクトを返す。
  • マッチが1つも見つからなければ、search()はPythonのNull値であるNoneを返す。

現段階で関心があるのはパターンがマッチするかどうかだけであり、それはsearch()の戻り値を見るだけで判断できる。'M'はこの正規表現にマッチする。なぜなら1つ目の省略可能なMはマッチし、2つ目と3つ目の省略可能なMは無視される。

In [12]:
re.search(pattern, 'M')
Out[12]:
<_sre.SRE_Match object; span=(0, 1), match='M'>

'MM'はマッチする。1つ目と2つ目の省略可能なMがマッチし、3つ目のMは無視される

In [13]:
re.search(pattern, 'MM')
Out[13]:
<_sre.SRE_Match object; span=(0, 2), match='MM'>

'MMM'はマッチする。3つすべてのMがマッチする

In [14]:
re.search(pattern, 'MMM')
Out[14]:
<_sre.SRE_Match object; span=(0, 3), match='MMM'>

'MMMM'はマッチしない。3つすべてのMがマッチするが、この正規表現は更に文字列がそこで終わっていることを要求している($という文字がある)。 しかし文字列はまだ終わっていない(4番目のMがある)。 したがってsearch()はNoneを返す。

In [15]:
re.search(pattern, 'MMMM') 

空の文字列もこの正規表現にマッチする。すべてのMは省略可能。

In [16]:
re.search(pattern, '')
Out[16]:
<_sre.SRE_Match object; span=(0, 0), match=''>

100の位をチェックする

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

  • 100 = C
  • 200 = CC
  • 300 = CCC
  • 400 = CD
  • 500 = D
  • 600 = DC
  • 700 = DCC
  • 800 = DCCC
  • 900 = CM

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

  • CM
  • CD
  • 0個から3個のC (100の位が0の場合に0個になる)
  • Dの後に、0個から3個のCが続いたもの。

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

  • 省略可能なDの後に、0個から3個のCが続いたもの。

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

このパターンは前の例と同じように始まっており、

  1. 文字列の最初であることをチェックし (^)、
  2. 1000の位をチェックする (M?M?M?)。
  3. ここからの括弧の中が新しい部分であり、同時に使われることのない3種類のパターン、

すなわちCM、CD、そしてD?C?C?C?(これは省略可能なDの後に、0個から3個のCが続いたもの)の3つが縦棒によって区切られて定義されている。正規表現のパーサはこれら3つそれぞれのパターンを(右から左へ)順番にチェックし、最初にマッチしたものを取り上げて、残りは無視する。

In [17]:
import re
pattern = '^M?M?M?(CM|CD|D?C?C?C?)$'

'MCM'(1900)はマッチする。なぜなら最初のMはマッチし、2番目と3番目のMは無視され、そしてCMがマッチするからだ(従ってCDとD?C?C?C?のパターンは試されてすらいない)。

In [18]:
re.search(pattern, 'MCM')
Out[18]:
<_sre.SRE_Match object; span=(0, 3), match='MCM'>

'MD'(1500)はマッチする。なぜなら最初のMはマッチし、2つ目と3つ目のMは無視され、D?C?C?C?のパターンはDにマッチするからだ(3つのCはそれぞれが省略可能なので無視される)。

In [19]:
re.search(pattern, 'MD') 
Out[19]:
<_sre.SRE_Match object; span=(0, 2), match='MD'>

'MMMCCC'(3300)はマッチする。なぜなら3つのM全部がマッチし、D?C?C?C?パターンがCCCにマッチする(Dは省略可能なので無視される)。

In [20]:
re.search(pattern, 'MMMCCC')
Out[20]:
<_sre.SRE_Match object; span=(0, 6), match='MMMCCC'>

'MCMC'はマッチしない。最初のMはマッチし、2つ目と3つ目のMは無視され、CMはマッチする。しかし、まだ文字列の末尾に到達していない(まだマッチしていないCがある)ので、$はマッチしない。CはパターンD?C?C?C?の一部としてはマッチしない。なぜならCMのパターンが既にマッチしているので、D?C?C?C?のパターンを用いる余地は無いからだ。

In [21]:
re.search(pattern, 'MCMC')

空の文字列は依然としてこのパターンにマッチする。 なぜなら全てのMは省略可能なので無視され、しかも空文字列はパターンD?C?C?C?(これらの文字はどれも省略可能なので無視される)にマッチするからだ。

In [22]:
re.search(pattern, '')
Out[22]:
<_sre.SRE_Match object; span=(0, 0), match=''>

{n,m}構文を使う

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

このパターンは「文字列の先頭にマッチし、0個以上3個以下のMにマッチし、次に文字列の終わりにマッチする」と言っている。 この0と3はどんな数値であっても構わない。もし1個以上3個以下のMにマッチさせたいのであれば、M{1,3}にすればよい。

In [23]:
pattern = '^M{0,3}$'

これは文字列の先頭にマッチし、次にありうる3つのうちの1つのMにマッチし、次に文字列の末尾にマッチする。

In [24]:
re.search(pattern, 'M')
Out[24]:
<_sre.SRE_Match object; span=(0, 1), match='M'>

これは文字列の先頭にマッチし、次にありうる3つのうちの2つのMにマッチし、次に文字列の末尾にマッチする。

In [25]:
re.search(pattern, 'MM')
Out[25]:
<_sre.SRE_Match object; span=(0, 2), match='MM'>

これは文字列の先頭にマッチし、次にありうる3つのうちの3つのMにマッチし、次に文字列の末尾にマッチする。

In [26]:
re.search(pattern, 'MMM')
Out[26]:
<_sre.SRE_Match object; span=(0, 3), match='MMM'>

この正規表現は文字列の末尾の前に最高で3つまでのMを許容するが、ここでは4つあるので、パターンはマッチせずNoneが返る。

In [27]:
re.search(pattern, 'MMMM')

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

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

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

10の位の特徴

  • 10 X
  • 20 XX
  • 30 XXX
  • 40 XL
  • 50 L
  • 60 LX
  • 70 LXX
  • 80 LXXX
  • 90 XC
In [28]:
pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$'

これは文字列の先頭にマッチし、次に最初の省略可能なMにマッチし、次にCMにマッチし、次にXLにマッチし、次に文字列の末尾にマッチする。

(A|B|C)構文は「AかBかCのどれか1つだけにマッチする」というものだった。

ここではXLにマッチしたので、XCとL?X?X?X?の選択肢は無視されて、文字列の末尾へ進む。MCMXLは1940をローマ数字で表したものだ。

In [29]:
re.search(pattern, 'MCMXL') # 1940
Out[29]:
<_sre.SRE_Match object; span=(0, 5), match='MCMXL'>

これは文字列の先頭にマッチし、

  • 次に最初の省略可能なMにマッチし、
  • 次にCMにマッチし、
  • 次にL?X?X?X?にマッチする。L?X?X?X?では、Lにマッチして選択的なXをすべてスキップする。 そして文字列の末尾に到達する。MCMLは1950をローマ数字で表したものだ。
In [30]:
re.search(pattern, 'MCML') # 1950
Out[30]:
<_sre.SRE_Match object; span=(0, 4), match='MCML'>

これは文字列の先頭にマッチし、次に最初の省略可能なMにマッチし、

  • 次にCMにマッチし、
  • 次に省略可能なLにマッチし、
  • 次に1つ目の省略可能なXにマッチし、2つ目と3つ目の省略可能なXは無視され、次に文字列の末尾にマッチする。
In [31]:
re.search(pattern, 'MCMLX') # 1960
Out[31]:
<_sre.SRE_Match object; span=(0, 5), match='MCMLX'>

これは文字列の先頭にマッチし、次に最初の省略可能なMにマッチし、次にCMにマッチし、次に省略可能なLにマッチし、次に3つすべての省略可能なXにマッチし、次に文字列の末尾にマッチする。MCMLXXXは1980をローマ数字で表したものだ。

In [32]:
re.search(pattern, 'MCMLXXX') # 1980 
Out[32]:
<_sre.SRE_Match object; span=(0, 7), match='MCMLXXX'>

まだマッチしていないXが残っている。従ってパターン全体はマッチせず、Noneを返す。MCMLXXXは有効なローマ数字ではない。

In [33]:
re.search(pattern, 'MCMLXXXX')

1の位の表現もこれと同様。

  • 1: I
  • 2: II
  • 3: III
  • 4: IV
  • 5: V
  • 6: VI
  • 7: VII
  • 8: VIII
  • 9: IX
In [34]:
pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$'

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

In [35]:
pattern = '^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$'
In [36]:
re.search(pattern, 'MDLV') # 1555
Out[36]:
<_sre.SRE_Match object; span=(0, 4), match='MDLV'>
In [37]:
re.search(pattern, 'MMDCLXVI')
Out[37]:
<_sre.SRE_Match object; span=(0, 8), match='MMDCLXVI'>

これは文字列の先頭にマッチし、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でマッチし、文字列の末尾にマッチする

In [38]:
re.search(pattern, 'MMMDCCCLXXXVIII') # 3888
Out[38]:
<_sre.SRE_Match object; span=(0, 15), match='MMMDCCCLXXXVIII'>

これは文字列の先頭にマッチし、

  1. 3個あり得るうちの0個のMにマッチし、
  2. D?C{0,3}には、省略可能なDを飛ばして3個あり得るうちの0個のCでマッチし
  3. 次のL?X{0,3}には、省略可能なLを飛ばして3個あり得るうちの0個のXでマッチし、
  4. 次のV?I{0,3}には、省略可能なVを飛ばして3個あり得るうちの1個のIでマッチし、文字列の末尾にマッチする。
In [39]:
re.search(pattern, 'I')
Out[39]:
<_sre.SRE_Match object; span=(0, 1), match='I'>

冗長な正規表現

正規表現をより保守しやすくしてくれる構文について。

以上の例を見て分かったように、このような正規表現は読み難く、 たとえその内容を一旦は理解できたとしても、6ヶ月後にまた理解できる保証が無いような代物。

ここで本当に必要なものはインラインのドキュメント。

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

  • 空白は無視される。 スペース、タブ、そして改行は、スペース、タブ、改行としてはマッチしない。これらは絶対にマッチしないのだ(冗長な正規表現でスペースにマッチさせたい場合は、その前にスラッシュを付けてエスケープする必要がある)。

  • コメントは無視される。 冗長な正規表現におけるコメントはPythonコードのコメントと同じようなもので、#という文字から始まり、その行の終わりまで続く。これはソースコードではなく複数行文字列の中にあるコメントだが、同じように機能する。

冗長な正規表現にするのだ。この例はそのやり方を示している。

In [40]:
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.VERBOSE を渡す必要がある。

re.VERBOSE はreモジュールで定義される定数であり、この定数はパターンを冗長な正規表現として扱わなければならないことを知らせる。見ての通り、このパターンはたくさんの空白(全て無視される)と、いくつかのコメント(全て無視される)を含んでいる。空白やコメントを無視してしまえば、これは前の節で見た正規表現とまったく同じだが、ずっと読みやすいものになっている。

In [41]:
 re.search(pattern, 'M', re.VERBOSE)
Out[41]:
<_sre.SRE_Match object; span=(0, 1), match='M'>
In [42]:
re.search(pattern, 'MCMLXXXIX', re.VERBOSE)
Out[42]:
<_sre.SRE_Match object; span=(0, 9), match='MCMLXXXIX'>
In [43]:
re.search(pattern, 'MMMDCCCLXXXVIII', re.VERBOSE)
Out[43]:
<_sre.SRE_Match object; span=(0, 15), match='MMMDCCCLXXXVIII'>

これはマッチしない。 なぜならre.VERBOSEフラグが与えられていないからだ。 従って、re.search関数はこのパターンをコンパクトな正規表現として扱い、 空白を無視せず、#も文字通りのものと解釈する。 Pythonは冗長な正規表現であるかどうかを自動で判定してはくれない。 明示的に冗長な正規表現だと宣言しない限り、Pythonはすべての正規表現をコンパクトな正規表現とみなす。

In [44]:
re.search(pattern, 'M')

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

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

この例は、私が現実に出会った問題から着想を得ている。 問題となったのはアメリカの電話番号のパース。 その時、顧客が求めてきたのは、電話番号を自由な形式で(1つのフィールドに)入力できるようにしつつ、そこから市外局番・局番・残りの番号、そしてオプションとして内線番号を取り出して会社のデータベースに別々に格納すること。

処理する電話番号の例:

  • 800-555-1212
  • 800 555 1212
  • 800.555.1212
  • (800) 555-1212
  • 1-800-555-1212
  • 800-555-1212-1234
  • 800-555-1212x1234
  • 800-555-1212 ext. 1234
  • work 1-(800) 555.1212 #1234

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

電話番号をパースする方法を構築

これは

  1. 文字列の先頭にマッチして、
  2. (\d{3})にマッチする。\dは任意の数字(0から9まで)を意味している。{3}は「ちょうど3つの数字にマッチする」という意味で、前に見た{n,m}構文の一種だ。 これを括弧の中に入れているのは「ちょうど3桁の数字にマッチさせ、マッチしたものを後で参照できるようにグループとして覚えておいてくれ」という意味
  3. 次にハイフンにマッチする。
  4. その次はまた別のちょうど3桁の数字のグループにマッチする。
  5. 次にハイフンにマッチする。
  6. その次はまた別のちょうど4桁の数字にマッチする。
  7. 次に文字列の終わりにマッチする。

re.compile(pattern, flags=0)

正規表現パターンを正規表現オブジェクトにコンパイルします。このオブジェクトは、以下で述べる match() と search() メソッドを使って、マッチングに使うことができます。

In [45]:
phonePattern = re.compile(r'^(\d{3})-(\d{3})-(\d{4})$')

正規表現のパーサが途中で記憶したグループにアクセスするには、 search() メソッドが返したオブジェクトにあるgroups()メソッドを使う。 このメソッドは正規表現で定義したグループがいくつあったとしても、それらをタプルで返してくれる。 この場合は、3つのグループを定義している。 そのうちの

  • 1つは3桁の数字で、
  • もう1つは3桁の数字、
  • 最後の1つは4桁の数字だ。
In [46]:
phonePattern.search('800-555-1212').groups()
Out[46]:
('800', '555', '1212')

内線番号を扱えるようにするには、この正規表現を拡張していく必要がある。

In [47]:
phonePattern.search('800-555-1212-1234') 

これはsearch()メソッドとgroups()メソッドを製品コードで「連鎖」させてはいけない理由を表している。正規表現がマッチしなかった場合には、search()メソッドは正規表現のマッチオブジェクトではなく、Noneを返す。

None.groups()を呼び出そうとすると、例外が送出されることになる。Noneはgroups()というメソッドを持っていない。

In [48]:
phonePattern.search('800-555-1212-1234').groups()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-48-7b8c064ea0cf> in <module>()
----> 1 phonePattern.search('800-555-1212-1234').groups()

AttributeError: 'NoneType' object has no attribute 'groups'

先ほどと同様に、

  1. 文字列の先頭にマッチし、
  2. 記憶される3桁の数字のグループにマッチし、
  3. ハイフンにマッチし、
  4. 記憶される3桁の数字のグループにマッチし、
  5. ハイフンにマッチし、
  6. 記憶される4桁の数字のグループにマッチする。

変更点は、ここから

  1. ハイフンにマッチし、
  2. 記憶される1桁以上の数字のグループにマッチし、
  3. 文字列の末尾にマッチ

することだ。

In [49]:
phonePattern = re.compile(r'^(\d{3})-(\d{3})-(\d{4})-(\d+)$')

groups()メソッドは4つの要素からなるタプルを返す。 正規表現に4つの記憶するグループを定義している。

In [50]:
phonePattern.search('800-555-1212-1234').groups()
Out[50]:
('800', '555', '1212', '1234')
In [51]:
phonePattern.search('800 555 1212 1234') 

この正規表現は完璧な答えでないどころか、先ほどのものから後退してしまっている。というのは、これは内線番号のない電話番号をパースできない。 欲しいのは、内線番号があればそれを読み取るが、仮に内線番号がなくとも、他の主番号の個々の部分について読み取ってくれるもの。

In [52]:
phonePattern.search('800-555-1212')

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

  1. 文字列の先頭にマッチして、
  2. 3桁の数字のグループにマッチして、
  3. \D+にマッチしている。 \Dは数字を除く全ての文字にマッチし、そして+は「1回以上」を意味している。 つまり\D+は1つ以上の数字ではない文字にマッチする。 異なる区切り文字にもマッチできるように、ハイフンの代わりにを使っている。
In [53]:
phonePattern = re.compile(r'^(\d{3})\D+(\d{3})\D+(\d{4})\D+(\d+)$')
In [54]:
 phonePattern.search('800 555 1212 1234').groups()
Out[54]:
('800', '555', '1212', '1234')
In [55]:
phonePattern.search('800a555b1212c1234')
Out[55]:
<_sre.SRE_Match object; span=(0, 17), match='800a555b1212c1234'>
In [56]:
phonePattern.search('80055512121234')

内線番号が無ければ読み取れないという問題もまだ修正されていない。 今のところ2つの問題を抱えていることになるが、両者は同じテクニックを使って解決できる。

In [57]:
phonePattern.search('800-555-1212')

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

In [58]:
phonePattern = re.compile(r'^(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$')

文字列の先頭にマッチして、次に記憶しておく3桁の数字 (800) のグループにマッチし、次に0個の数字ではない文字にマッチし、次に記憶しておく3桁の数字 (555) のグループにマッチし、次に0個の数字ではない文字にマッチし、次に記憶しておく4桁の数字 (1212) のグループにマッチし、次に0個の数字ではない文字にマッチし、次に記憶しておく任意桁の数字 (1234) にマッチし、次に文字列の末尾にマッチする。

In [59]:
phonePattern.search('80055512121234').groups()
Out[59]:
('800', '555', '1212', '1234')

他のパターンも扱える: ハイフンの代わりにドットを使ったものや、内線番号の前にスペースとxがあるものでも対応できる。

In [60]:
phonePattern.search('800.555.1212 x1234').groups()
Out[60]:
('800', '555', '1212', '1234')

内線番号は再び省略可能になった。 内線番号が見つかれば、groups()メソッドは4つの要素をもったタプルを返してくれるし、見つからなければ4番目の要素は単に空の文字列になる。

In [61]:
phonePattern.search('800-555-1212').groups()
Out[61]:
('800', '555', '1212', '')

市外局番の前に余計な文字があるが、正規表現は文字列が市外局番から始まることを想定しているのだ。しかし問題はない。 「0個以上の数字でない数」のテクニックを使うことで、市外局番の前にある文字を読み飛ばすことができる。

In [62]:
phonePattern.search('(800)5551212 x1234')

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

これは基本的に前の例と同じだが、最初に記憶されるグループ(市外局番)の前で、 \D*(0個以上の数字ではない文字)にマッチさせている点が異なる。 これらの数字でない文字は記憶されないことに注意。

もしそれらが見つかっても単に読み飛ばされてしまう。そして市外局番に行き着くと、それ以降の数字が記憶され始めるのだ。

In [63]:
phonePattern = re.compile(r'^\D*(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$')
In [64]:
phonePattern.search('(800)5551212 ext. 1234').groups() 
Out[64]:
('800', '555', '1212', '1234')
In [65]:
phonePattern.search('800-555-1212').groups()
Out[65]:
('800', '555', '1212', '')

なぜなら市外局番の前に1があるが、市外局番の前に来るものは全て数字でない文字(\D*)だと想定していた。

In [66]:
phonePattern.search('work 1-(800) 555.1212 #1234')

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

この正規表現には ^ が無いことに注意。 もう文字列の先頭にはマッチさせない。 正規表現を入力全体とマッチさせる必要はどこにもない。 正規表現エンジンが入力文字列にマッチし始める位置を頑張って見つけ出し、そこからマッチを行ってくれる。

In [67]:
phonePattern = re.compile(r'(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$')
In [68]:
phonePattern.search('work 1-(800) 555.1212 #1234').groups()
Out[68]:
('800', '555', '1212', '1234')
In [69]:
phonePattern.search('800-555-1212')
Out[69]:
<_sre.SRE_Match object; span=(0, 12), match='800-555-1212'>
In [70]:
phonePattern.search('80055512121234')
Out[70]:
<_sre.SRE_Match object; span=(0, 14), match='80055512121234'>

まだ最終バージョンの正規表現を理解しているうちに、 どうしてこのように構成したのかを忘れないうちに冗長な正規表現として書いておこう。

In [71]:
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)
In [72]:
phonePattern.search('work 1-(800) 555.1212 #1234').groups()
Out[72]:
('800', '555', '1212', '1234')
In [73]:
phonePattern.search('800-555-1212')
Out[73]:
<_sre.SRE_Match object; span=(0, 12), match='800-555-1212'>

まとめ

  • ^ は文字列の先頭にマッチする。
  • $ は文字列の末尾にマッチする。
  • \b は単語境界にマッチする。
  • \d は任意の数字にマッチする。
  • \D は数字以外の任意の文字にマッチする。
  • x? はxと言う文字に任意でマッチする(言い換えると、xが0回または1回現れるものにマッチする)。
  • x* はxが0回以上現れるものにマッチする。
  • x+ はxが1回以上現れるものにマッチする。
  • x{n,m} はxがn回以上、m回以下現れるものにマッチする。
  • (a|b|c) はa, b, cのどれか一つだけにマッチする。
  • (x) は一般に記憶されるグループのこと。 re.searchメソッドが返したオブジェクトにあるgroups()メソッドを使うことで、マッチした値を取得できる。

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

参考リンク