Pythonで正規表現を使うには
正規表現とは、文字列のパターンマッチングを行うための表記方法を示すものです。正規表現は英語で「Regular Expressions」と呼び、Pythonで使用するには組み込みモジュールのreを用います。reモジュールは、 ”import re” でライブラリを読み込み、必要な関数を使用することができます。
【参考】:Python Docs: 正規表現 HOWTO 【参考】:Python Docs: re --- 正規表現操作
正規表現の役割とメリット
私たちの扱うデータには、単語や文字、文章など多くの種類があり、記号なども含めると膨大です。これらを扱うために、プログラミング言語では文字と文字列で表し、Pythonでは文字の集合体を文字列で表します。
正規表現は、このような膨大な文字列から任意の文字を抽出するために用いる1つの表記形式を指します。文字列の集合体から、決められたパターンのデータが含まれているか確認するために用います。抽出が必要な文字列すべてを記載することなく、パターンとして与えることで、求める文字列を簡単に探せます。
正規表現を必要とするケース
データの処理で特定パターンに合致するパターンマッチングができると、効率的にデータの抽出ができます。
例えばウェブページで見られる、 ”For more information, please call 0120-345-6789” のような文章があり、電話番号の数字だけ取り出したい場合、どうすればいいのでしょうか。このような場合に正規表現を用いると、簡単に数字を取り出すことができます。
正規表現の表記法
ここでは、先の電話番号を取り出すケースのような、文字列から数字だけを取り出す場合を考えてみます。この場合、正規表現で任意の数字を表すには、 ”\d” あるいは ”[0-9]” が用意されています。
このような正規表現は文字の種類ごとに定義されており、波括弧(”{ }”)、角括弧(””)、丸括弧(”( )”)や ”^$.?” などの特殊な文字を用いて表記されます。Pythonの正規表現で用いる記法は、大まかに特殊文字と特殊シーケンスに分けられます。詳しくは、以降で解説していきます。
【参考】:Python Docs: 正規表現 HOWTO 【参考】:Python Docs: re --- 正規表現操作
特殊文字の概要と一覧
特殊文字は、それ自身が対象とする文字列に含まれているものではなく、文字列を代わりに表現するための置換文字を表します。一覧で表すと次のように分類付けられており、用途に応じて記述が可能です。
ここでは、使用例も合わせて掲載しておきます。
特殊シーケンスの概要と一覧
特殊シーケンスは、エスケープシーケンスと同様に特殊文字バックスラッシュ ”” で表す正規表現です。文字リテラルと同様に、バックスラッシュに続けてエスケープが必要な文字を指定します。バックスラッシュのみで正規表現が構成されるわけではなく、続く文字指定によってパターンを切り替えます。
具体的には次のようなものがあります。
Pythonで正規表現を使ってみる
Pythonでは、組み込みモジュールを用いて正規表現を扱います。正規表現で任意の数字を表すには、 ”\d” あるいは ”[0-9]” を使用します。ここでは、文字列が数字なのかパターンマッチングを行います。
例えば、文字列 "1234567890ABC" から先頭の数字のみを取り込む場合は、 ”^\d+” と記述します。 ”^” は先頭を意味し、 ”\d” は数字、数字の後の ”+” は1回以上の繰り返しです。なお、Pythonの文字列は、シングルクォーテーション(’)とダブルクォーテーション(”)で違いはありません。
パターンマッチングを実際に行うには、reモジュールの関数を用います。ここでは、re.match()関数を用いて以下のように求めることができます。
import re
textstr="1234567890ABC"
pattern="^\d+"
ret = re.match(pattern, textstr)
if ret:
print(ret) # “<re.Match object; span=(0, 10), match='1234567890'>”が表示され、10文字目までマッチしたことがわかります
print(ret.group()) # “1234567890”が表示されます
【参考】:Python Docs: 正規表現 HOWTO 【参考】:Python Docs: re --- 正規表現操作
正規表現のパターンをオブジェクトに置き換える
正規表現を実行時に都度用いると、複雑なパターンでは処理時間がかかるデメリットがあります。処理を効率的に行うため、Pythonでは事前にパターンオブジェクトにコンパイルが可能です。ここでは、さきほど数字を取り込むために用いた正規表現である 、 ”^\d+” を事前コンパイルしてみます。
コンパイルはre.compile()を用い、match()オブジェクトでパターンマッチングの結果を得ます。具体的には、次のように記述します。
import re
textstr="1234567890ABC"
pattern="^\d+"
p = re.compile(pattern)
ret = p.match(textstr)
if ret:
print(ret) # “<re.Match object; span=(0, 10), match='1234567890'>”が表示され、10文字目までマッチしたことがわかります
print(ret.group()) # “1234567890”が表示されます
この方法は、後述するre.search()でも同様の使い方が可能です。
パターンマッチングの関数の違い
パターンマッチングの関数では、先に紹介したre.match()関数に加えて、re.search()関数が提供されています。いずれもパターンマッチングに利用しますが、re.match()関数は文字列の先頭からパターンを探すのに対して、re.search()関数はマッチする最初の場所を文字列全体から探す点に違いがあります。
re.match()関数の使い方
re.match()関数は、reモジュールの代表的な関数でパターンマッチングに用います。
re.match()の戻り値は、matchオブジェクトで文字列の先頭から探します。マッチしない場合はNoneを返します。
matchオブジェクトのgroup()メソッドは、正規表現にマッチした文字列を返します。start()は開始位置、end()は終了位置、span()は開始既知と終了位置をタプルで返します。
ここでは、冒頭で示した "For more information, please call 0120-345-6789" から電話番号を取り込むサンプルを示します。文頭の英字はスキップし、数字のみをグループインデックス1に格納しています。なお、グループの考え方はここでは省略し、後半で解説します。
import re
textstr= "For more information, please call 0120-345-6789"
pattern=”\D+(\d{2,4}-\d{1,4}-\d{4})”
ret = re.match(pattern, textstr)
if ret:
print(ret) # "<re.Match object; span=(0, 47), match='For more information, please call 0120-345-6789'>"が表示されます
print(ret.group(1)) # 0120-345-6789が表示されます
print(ret.start(1)) # 34が表示されます
print(ret.end(1)) # 47が表示されます
print(ret.span(1)) # (34, 47)が表示されます
この例では、ret.group()は該当する英数字を含む文字列全体、ret.group(1)は数字部分だけが格納されています。ret.group(1)では、行頭の非数字の文字は ”\D+” で取り除き、電話番号の部分は、 ”(\d{2,4}-\d{1,4}-\d{4})” で表しています。
電話番号はハイフン付きの場合、固定電話やフリーダイヤル、携帯番号などで文字数に違いがあります。この例では、 ”03” などで始まる市外局番や ”0120-345-6789” 、携帯電話の ”090-1234-5678” でもマッチするように、最初の数字は2~4字、2番目の数字は1~4字、3番目は4字としています。
【参考】:Python Docs: re --- 正規表現操作 Pattern..match()
re.search()関数の使い方
先ほどのre.match()を使って電話番号を取り出す例では、行頭の非数字の文字を ”\D+” で除外する必要がありました。ここで紹介するre.search()関数では、マッチする場所を全体から探しますので、 ”\D+” を付けずに探せます。その結果、簡単な指定だけで求める文字列を探すことが可能です。
import re
textstr= "For more information, please call 0120-345-6789"
pattern=”(\d{2,4}-\d{1,4}-\d{4})” # ”\D+”を付けずに探すことができます
ret = re.search(pattern, textstr)
if ret:
print(ret) # "<re.Match object; span=(0, 47), match='For more information, please call 0120-345-6789'>"が表示されます
print(ret.group()) # 0120-345-6789が表示されます
print(ret.start()) # 34が表示されます
print(ret.end()) # 47が表示されます
print(ret.span()) # (34, 47)が表示されます
これらのことから、文字列の先頭から探す場合はre.match()関数を使い、文字列全体から探す場合はre.search()関数を使うと効率的に使用できることがわかるでしょう。
【参考】:Python Docs: re --- 正規表現操作 Pattern.search()
正規表現をグルーピングして使用する
正規表現の丸括弧 ”( )” は、グループの開始と終了を意味します。この組み合わせを工夫してさらに細かくグルーピング(グループ化)も可能です。グループ化することによって、それぞれの文字列をグループ毎に効率的に扱うことが可能になります。
ここでは、先ほどの電話番号を探す正規表現の ”(\d{2,4}-\d{1,4}-\d{4})” を、次のように "(\d{2,4})-(\d{1,4})-(\d{4})" と変更し、ハイフンを除いた3つのグループを作成してみます。
import re
textstr= "For more information, please call 0120-345-6789"
pattern=”(\d{2,4})-(\d{1,4})-(\d{4})” # ”ハイフンを除いた3つのグループに分けます
ret = re.search(pattern, textstr)
if ret:
print(ret) # "<re.Match object; span=(0, 47), match='For more information, please call 0120-345-6789'>"が表示されます
print(ret.group()) # 0120-345-6789が表示されます
print(ret.group(1)) # 0120が表示されます
print(ret.group(2)) # 345が表示されます
print(ret.group(3)) # 6789が表示されます
これによって、re.search()が返すオブジェクトgroup()にgroup(1)、group(2)、group(3)の値が格納されます。それぞれハイフンで分割した数字が格納されています。start()、end()、span()にもそれぞれのグループに応じたインデックスに値が格納されています。
グルーピングによって簡単に電話番号の市外局番を統合管理したり、用途別に仕分けしたりすることが可能です。
Pythonの正規表現は活用の幅が広い
これまでに、正規表現を扱うre.match()やre.search()関数を取り上げました。この他にも、re.fullmatch()関数は、文字列全体のパターンマッチングの結果を返し、re.split()関数は、指定したパターンで文字列を分画します。文字列の置換を行う関数も提供されています。
このように、Pythonの活用方法は幅広いため、基本を学んだらさらに多くの関数を活用することで、効率的なパターンマッチングが実現できるでしょう。
編集部オススメコンテンツ
アンドエンジニアへの取材依頼、情報提供などはこちらから