正規表現(regular expression)とは文字列のパターンを表現するための表記方法で、文字列のパターンマッチングに使われます。 たとえば「任意の1文字」と表現するときは ‘.’ (dot) と表記し、どんな文字でも1文字にマッチします。‘^’ (caret)は文字列の先頭を意味し、‘$’(dollar sign)は文字列の最後を意味します。’^.$’は任意の文字が1文字のみの行を表現しています。
Pythonではライブラリreで提供されています。
ライブラリreを使って簡単な文字列の中に正規表現で表現したパターンが存在しているかどうか調べてみるプログラムを書いてみます。
# File: resample.py
#
# 正規表現ライブラリ re を使ってのマッチング
#
# https://docs.python.org/ja/3/library/re.html
#
import re
# 文字列を用意する
textSet = ["abc","feg","hij","cba","gef","jih","afh","bei","cgj"]
# マッチさせる正規表現のパターン
regexp_pattern="^ab"
for text in textSet:
result=re.search(regexp_pattern,text) # 文字列の中からパターンを探す
if result != None : # マッチしたら
print(regexp_pattern,text,result.group(),result.span())文字列”abc”から”cgj”まで色々な(3文字の)文字列がリストに用意されています。このリストの中から先頭が”ab”から始まる文字列 (‘^ab’) をサーチしています。出力はマッチしなければNoneを返し、マッチしたならばオブジェクト(<class ‘re.Match’>)を返します。result.group()がマッチした文字列を、result.span()はマッチした文字列内の位置をタプルで返しています。
textSetに色々な文字列、regexp_patternに色々な正規表現のパターンを入れて試してみましょう。
(注意) \s (バックスラッシュにアルファベット小文字のs : 意味は「1つの空白」) のような正規表現を使う場合 r"\s" と文字列の前に r (アルファベット小文字のr)を加えて、この文字列は正規表現であることを明示する必要があります。rをつけなくても動作はしますが、 "SyntaxWarning: invalid escape sequence '\s' " のようなワーニングが出力されます。
正規表現の記法の詳細に関してはPythonのオンラインマニュアルで正規表現reを確認して欲しいのですが、ここではよく使われる記法についていくつか紹介します。
* (asterisk)は、直前の正規表現を0回以上返したものにマッチします。‘ab*’とすると、’a’、‘ab’、‘abb’、‘abbb’(連続したb)にマッチします。サンプルコードresample.pyで試すと’b’が連続したものがないのでわかりづらいですが、’ab*‘は’abc’、‘cba’、’afh’にマッチします。
+ (plus)は、直前の正規表現を1回以上返したものにマッチします。‘ab+’とすると、’ab’、‘abb’、‘abbb’(連続したb)にマッチします。サンプルコードresample.pyで試すと’b’が連続したものがないのでわかりづらいですが、’ab+’は’abc’にマッチします。
? (question) 直前の正規表現を0回もしくは1回返したものにマッチします。サンプルコードresample.pyで試すと、‘ab?’は’abc’、‘cba’、’afh’にマッチします。
[] (square brackets)は複数の使い方があります。
では、青空文庫のテキスト中にあるルビを抜き出すサンプルコードを書いてみます。 正規表現は”《[あ-ん]*》“となります。‘《’で始まり、ひらがな「あ」から「ん」までのいずれか1つ以上の文字列からなり、最後は’》’で終わるパターンを意味します。 文字列の中でマッチした部分すべてが欲しいのでre.findall()を使います。
# File: rubymatch.py
# 青空文庫のテキスト中にあるルビを抜き出す
#
import re
with open("text01.txt",encoding="utf-8") as f:
text = f.read()
match_ruby=re.findall("《[あ-ん]+》",text)
##print(match_ruby)
print("ルビの数: ",len(match_ruby))(注)ここでは話を簡単にするために長音記号や拗音のように小さい「ぁ」はひとまずおいておきます。
re.subを使えば正規表現でマッチした文字列を置き換えることが出来ます。テキストのルビの部分を”【削除】“という文字列に置き換えるには次のようにします。もし”【削除】“という部分の文字列を”“とするとルビとしてあった文字列が消える(空白文字列)に置き換わります。
# File: resubsample.py
# 正規表現ライブラリ re を使っての正規表現でマッチング
# した文字列を指定の文字に変換する
#
# https://docs.python.org/ja/3/library/re.html
#
import re
with open('text01.txt',encoding='utf-8') as f:
text = f.read()
print(re.sub(r"《[あ-ん]*》","【削除】",text))応用として、テキストの中の会話文を取り出すための書き方は次のようになります。
# File: resubsample2.py
# 正規表現ライブラリ re を使っての正規表現でマッチング
# した文字列を指定の文字に変換する その2
#
# https://docs.python.org/ja/3/library/re.html
#
import re
with open('text01.txt',encoding='utf-8') as f:
text = f.read()
pre_text=re.sub("《[あ-ん]*》","",text)
speaks=re.findall("「.*?」",pre_text) # 最小マッチ
for t in speaks:
print(t)事前にre.compile()を使い正規表現のパターンをインスタンス変数として用意して利用する方法があります。 通常では、re.search()やre.match()で十分に高速に処理できますが、正規表現をインスタンス変数として用意しておきたい時などに使います。
# File: recompsample.py
# 正規表現ライブラリ re を使ってのマッチング
# 事前にcompileさせる方式
#
# https://docs.python.org/ja/3/library/re.html
#
import re
# 文字列を用意する
textSet = ["abc","feg","hij","cba","gef","jih","afh","bei","cgj"]
# マッチさせる正規表現のパターン
regexp_comp=re.compile("[a-zA-Z0-9]+j")
for text in textSet:
result=regexp_comp.match(text) # 文字列の中からパターンを探す
if result != None : # マッチしたら
print(text)ひらがなをだけを抜き出す場合、小書きの「ぁ」などや拗音「きゃ」の「ゃ」も含めて取り出したいときや、カタカナ語の「カチューシャ」のような長音を含んだ単語を取り出したい時は次のような正規表現が参考になります。尚、文字はいずれも UTF-8 (UNICODE) を前提としています。
## ひらがな
re.findall("[\u3041-\u3096]+", text)
re.findall("[ぁ-ゖ]+", text)
##カタカナ語
re.findall("[\u30A1-\u30FA|\u30FC]+", text)
re.findall("[ァ-ヺ|ー]+", text)
これまで記号の呼び方は特に説明していませんでしたが、正規表現につかう、‘()’ ‘{}’ ‘[]’ などは、色々な記号の読み方があります。筆者は ‘()’は「カッコ(左〜・右〜)」、’{}‘は「ブレース(左〜・右〜)」、’[]‘は「ブラケット(左〜・右〜)」と呼んでいます。JIS X 0213の呼称では参考として紹介されているだけで、たとえば’(‘は「始め小括弧/始め丸括弧」、’{‘は「始め中括弧/始め波括弧」、’[’は「始め大括弧/始め角括弧」と併記になっています。人により、いろいろな記号の呼び方をしており、お互いの呼び方が違い、時々通じないことがあり困ることがあります。記号の読み方をまとめているわかりやすい表を作られた方がいるので、参考にすると良いでしょう。
Pythonの公式サイトには 正規表現HOWTO という正規表現に関するHOWTOが掲載されています。非常に役に立つので、ぜひとも参考にしてください。