Pythonでの正規表現の使い方を勉強した。Perlの正規表現とパターンの書き方はほぼ同じのようだけど、その他の部分はかなり違っている。あまり良いページが見つけられなかったので、正規表現 HOWTO — Python 3.3.3 ドキュメントを読んで簡単にまとめた。
この記事の想定読者は、他言語で正規表現を扱ったことのある人。したがって正規表現のパターンの書き方についてはこの記事の対象外。正規表現のチュートリアル的な記事が読みたければ、Python3なら正規表現 - Dive Into Python 3 日本語版、Python2ならChapter 7. Regular Expressions(英語)が良いと思う。
基本
re.compile()を用いて、あらかじめ正規表現のパターンをコンパイルする。例えば、「3で始まり7で終わる最短の文字列」を探すためのパターンは以下のように書ける。
文字列の前にrを付けるのが普通。そうすると、文字列中のバックスラッシュ文字をそのままバックスラッシュとして扱えるので、パターンの書き方が煩雑にならない。
import re pattern = re.compile(r'3.*?7') # 3で始まり7で終わる最短の文字列
4種類のパターンマッチ方法
パターンとのマッチを探すための関数には、match(), search(), findall(), finditer()の4種類がある。
例として、以下の文字列から、あるパターンに合致した部分を探す場合を考えよう。
text = '01234567890123456789'
match()
最初はmatch()関数。これは、文字列の先頭でパターンがマッチするかどうかを判定する関数。
text = '01234567890123456789' pattern = re.compile(r'0.*?4') # 0で始まり4で終わる最短の文字列 # 文字列の先頭でマッチするかどうか判定 matchObj = pattern.match(text) if matchObj: print matchObj.group() # '01234'
マッチするのでmatchObjにはmatchObjectオブジェクトが入る。このオブジェクトから、マッチした部分を取り出すには.group()関数を使う。他の関数については、すぐ下のsearch()にて説明する。
search()
次にsearch()関数。これは、文字列の中にパターンとマッチする部分があるかどうかを判定する関数。match()と違い、パターンが文字列の先頭になくてもマッチしてくれる。ただし、マッチする箇所が複数ある場合でも、最初の箇所しか返してくれない。
text = '01234567890123456789' pattern = re.compile(r'3.*?7') # 3で始まり7で終わる最短の文字列 # 文字列を走査して正規表現がどこにあるか調べる matchObj = pattern.search(text) if matchObj: print matchObj # <_sre.SRE_Match object at 0xNNNNNNNNN> print matchObj.group() # マッチした文字列を返す # 34567 print matchObj.start() # マッチの開始位置を返す # 3 print matchObj.end() # マッチの終了位置を返す # 8 print matchObj.span() # マッチの位置(start, end)を含むタプルを返す # (3, 8)
findall()
次はfindall()関数。これは、文字列の中のパターンとマッチする部分をすべてリストとして返す関数。search()と違って、マッチする箇所をすべて取得可能だが、戻り値はmatchObjectオブジェクトではなく、ただの文字列のリストである。search()を強化している部分と弱化している部分があり、痛し痒しである。
text = '01234567890123456789' pattern = re.compile(r'3.*?7') # 3で始まり7で終わる最短の文字列 # パターンにマッチしたすべてをリストとして返す matchedList = pattern.findall(text) if matchedList: print matchedList # ['34567', '34567']
finditer()
最後はfinditer()関数。これは、文字列の中のパターンとマッチする部分をイテレータで返す関数。イテレータをなめることでsearch()関数と同等のことができるので、マッチに関する詳しい情報が得られる。Perlでいう/pattern/g と同じようなことができると思えばよい。
実のところfinditer()関数さえあれば、他の関数でできることはすべてできてしまう。
text = '01234567890123456789' pattern = re.compile(r'3.*?7') # 3で始まり7で終わる最短の文字列 # パターンにマッチしたすべてをイテレータとして返す iterator = pattern.finditer(text) for match in iterator: print match.group() # 1回目: 34567 2回目: 34567 print match.start() # 1回目: 3 2回目: 13 print match.end() # 1回目: 8 2回目: 18 print match.span() # 1回目: (3, 8) 2回目: (13, 18)
正規表現のパターンをコンパイルしない方法
正規表現を使い回す必要がないときは、モジュールレベルの関数を使うと簡単に書ける。
matchedList = re.findall(r"3.*?7", "01234567890123456789") # ↑は # pattern = re.compile(r'3.*?7') # matchedList = pattern.findall("01234567890123456789") # と同じ
大文字小文字の違いを無視してマッチ
Perlでいう/pattern/iと同じ事をするには、re.IGNORECASEというコンパイルフラグを付与する。
pattern = re.compile(r"day", re.IGNORECASE) # re.Iでも可 matchedList = pattern.findall("1day2Day3DAY") print matchedList # ['day', 'Day', 'DAY']
マッチした文字の利用
Perlだと/(.*)(.*)/でマッチした時に、$1で最初の括弧の中の文字列が、また$2で2番目の括弧の中の文字列が取り出せた。Pythonでは、group(1)によってPerlの$1に相当するものが、またgroup(2)によってPerlの$2に相当するものが取り出せる。また、groups()とすると(group(1), group(2))がタプルで返る。
ついでに、group(0)で取り出せるのは、上の方で紹介したgroup()と同じ。
text = "[width=30, height=45] [width=40, height=20]" pattern = re.compile(r'\[width=(.*?), height=(.*?)\]') iterator = pattern.finditer(text) for match in iterator: print match.group(0) # 1回目: [width=30, height=45] 2回目: [width=30, height=45] print match.group(1) # 1回目: 30 2回目: 40 print match.group(2) # 1回目: 45 2回目: 20 print match.groups() # 1回目: ('30', '45') 2回目: ('40', '20')