[python3] 正規表現を使ったパターンマッチング

Posted on 2019/03/28 in programming , Updated on: 2019/03/30

はじめに

大量の文字列の中から、容易に見つけたい文字列を検索することができる正規表現を python で実装する方法。

正規表現パターンについては、別記事[python3] 短縮系や日本語対応など正規表現まとめ一覧を参照。

reモジュール

python の正規表現関数は、reモジュール内にある。よって使用する際にはインポートが必要。re モジュールは正規表現パターンを使用した検索、置換、連結、分割などの便利なメソッドを持つ。これらのメソッドは,マッチした文字列や、MatchObject(マッチオブジェクト)を返す。MatchObject は正規表現にマッチした文字列、マッチした文字列の開始位置、終了位置などの情報をもっている。

import re

match() メソッド

match()メソッドは、文字列の先頭で正規表現とマッチするかを判定する。第一引数に正規表現を、第二引数に検索する文字列を入れる。ヒットするとMatch objectが返ってくる。

m = re.match('a.c', 'abc')
print(m)
# <re.Match object; span=(0, 3), match='abc'>

返ってきた Match object には、検索した文字列内で正規表現にマッチしたインデックスを表すspanや、マッチした文字列を示すmatchが含まれる。それぞれ下記のようにメソッドで取り出すことができる。

print(m.span())
# (0, 3) ⇒ インデックス0~3にマッチした文字列がある。

print(m.start())
# 0

print(m.end())
# 3

print(m.group())
# 'abc'

search() メソッド

search()メソッドは、文字列内を走査して、一番初めに見つけた正規表現とマッチする。

m = re.search('a.c', 'defg abc defg abc')
print(m)
# <re.Match object; span=(5, 8), match='abc'>

上記例では、abc が2つ入っている文字列を検索しているが、一番初め(インデックス5~8)に見つかった abc にしかマッチしていないことがわかる。

findall() メソッド

search()とは異なり、findall()メソッドは、正規表現にマッチする文字列全てを探し、リストとして返す。

m = re.findall('a.c', 'defg abc defg abc')
print(m)
# ['abc', 'abc']

finditer() メソッド

finditer()メソッドは、重複しないマッチオブジェクトのイテレータを返す。

- イテレータ リストなどの複数の要素をもったデータ型に対して、順番にデータを取り出す機能を提供するもの。

m = re.finditer('a.c', 'defg abc defg abc')
print(m)
# <callable_iterator at 0x344ab90>

イテレータの中身を見るときは、リスト内包表記を使って

# m から Matching object を一つづつ w として取り出し、group()メソッドで
# マッチした文字列をリストに追加する。
print([w.group() for w in m])
# ['abc', 'abc']

compile() メソッド

re.compile()に正規表現パターンを表す文字列を渡すと、正規表現をコンパイルした Regex パターンオブジェクトを返す。同じ正規表現をひとつの Python スクリプト内で繰り返し使う際には、事前にコンパイルを1度だけして繰り返し使うことになるので、高速に動作する。

regex = re.compile('正規表現')

m1 = regex.search('検索文字列1')
m2 = regex.search('検索文字列2')
m3 = regex.search('検索文字列3')

re.VERBOSE

URLを検索する際などに、正規表現が長くなってしまうことがある。この際に、正規表現をトリプルクウォートで囲い、re.compile()の第二引数として、re.VERBOSEを追加することで、長くなる正規表現を改行し、コメントを追加することができる。この際、コメント前のスペースは正規表現に含まれない。 (※ 下記例の"""前のrについては、下の項目raw文字列を渡すを参照)

phone_number_regex = re.compile(r"""
                                \d\d-      # 市外局番
                                \d\d\d\d- # 電話番号 1
                                \d\d\d\d- # 電話番号 2
                                """, re.VERBOSE)

split() メソッド

"My, name, is, SAIRA" という文字列から、アルファベットの単語のみ取り出したい場合(コンマを除く)に、正規表現とsplit()メソッドを組み合わせて実現する。

s = "My, name, is, SAIRA"

# 正規表現を使わずに、split()を使うとコンマが含まれてしまう。
print(s.split())
# ['My,', 'name,', 'is,', 'SAIRA']

# 正規表現 + split()
p = re.compile('\W+')
print(p.split(s))
# ['My', 'name', 'is', 'SAIRA']

sub() メソッド

正規表現でマッチした文字列を、sub()の第一引数に渡す文字列で置換する。第二引数には、検索する文字列を渡す。

p = re.compile("(a)")
print(p.sub("SAIRA", "This blog's writer is a."))
# This blog's writer is SAIRA.

raw文字列を渡す

複雑な正規表現を定義するときは、クォーテーションの前に、raw文字列 r を付て利用するのがいい。

Pythonでは、文字列\nは"バックスラッシュと小文字のn"ではなく、1文字の改行文字を表現する。"バックスラッシュと小文字のn"を表現したい場合は、エスケープ文字のバックスラッシュを足して\\nと表現する必要がある。

正規表現にはバックスラッシュを使用したパターンが存在するため、毎回エスケープ文字を入れると複雑になってしまう。そこで、クウォートの前に、r を付けることによって raw 文字列として扱われ、エスケープシーケンスを無効にしてくれる。

例) 数字2ケタ-数字4ケタ-数字4ケタ の正規表現

# raw 文字無し
'\\d\\d-\\d\\d\\d\\d-\\d\\d\\d\\d'

# raw 文字有り
r'\d\d-\d\d\d\d-\d\d\d\d

greedy 貪欲マッチ

Python の正規表現は、デフォルトでは複数の可能性がある場合にはもっとも長いものにマッチする。(greedy:貪欲マッチ)

# Good が2~4回繰り返し文字列にマッチ
# この場合、GoodGood ではなく、
# GoodGoodGoodGood にマッチする。
greedy_regex = re.compile(r'(Good){2,4}')
m = greedy_regex.search('GoodGoodGoodGood')
print(m.group())
# GoodGoodGoodGood

もっとも短いものにマッチさせたいときは、正規表現の最後に?を付ける。(not-greedy:非貪欲マッチ)

not_greedy_regex = re.compile(r'(Good){2,4}?')
m = greedy_regex.search('GoodGoodGoodGood')
print(m.group())
# GoodGood