Python

【Python】標準ライブラリの活用(正規表現:re)

標準ライブラリの活用(正規表現:re) Python

前回の記事でPythonの標準ライブラリの中からCSVファイル操作の(csv)モジュールについて、その利用方法を説明しました。

今回も、Pythonの標準ライブラリの中から汎用性の高いものについてピックアップして学んでいきたいと思います。

今回は正規表現に関するreモジュールを紹介します。

この記事を読んで分かること
  • Pythonの標準ライブラリの使い方がわかる
  • 正規表現(re)のモジュールについての使い方が分かる
  • 置換時の後方参照について使い方がわかる
スポンサーリンク

正規表現とは

正規表現とは何でしょうか?

正規表現はPython以前からあるプログラム言語やUNIXなんかではよく使われるものなのですが

例えばあるテキストがあった場合に、そのテキストの中から必要な部分を切り出して処理する作業はプログラムとしてもよくあると思います。

ただ、その場合

  • Webページのテキストからメールアドレス、URLの情報を抜き出す
  • HTMLのテキストから特定のタグだけを切り出して、Webページのタイトル部分のみ収集する

こういった作業は、単純な検索機能ではなかなか難しかったりします

メールアドレスって@マークで検索すれば良いのかな、それからどこからどこまでがメールアドレスなのかをプログラム上でどうやって判断するのかな?

こうした時にある特定の文字列を詳細に検索する際に利用するのが「正規表現」です。

テキストパターンを使って検索

正規表現とは、文字列の集合を「パターン」を使って表現するための方法です。

パターンとは、例えば「何桁かの数字」や「半角英文字だけで出来ている単語」というように、探したいテキストの特徴を定義するものです。

このパターンをテキストと特殊な書き方の記号を組み合わせて作成するのですが、このパターンを元にテキスト内からパターンに当てはまる部分を探し出していきます。

パターンを使って文字列を詳細に検索していくイメージですね

普通の検索では、検索文字と一致するテキストしか探すことが出来ませんが、パターンを使うことで例えば「数字だけが並んだもの」や「半角英文字だけの単語」といった、詳細にパターンで指定したものと合致するテキストを探し出すことができるようになります。

検索では完全一致や部分一致したテキストしか見つけられないが、パターンを使う正規表現はパターンに合致したテキスト全てを見つけることができる。

スポンサーリンク

reモジュールについて

正規表現の機能はPythonでは「re」モジュールとして用意されています。

それでは主な関数をいくつかみていきましょう。

match関数

matchは調べる文字列の先頭が指定のパターンと一致する(パターンが当てはまる)かどうかを調べる関数です。

あくまで先頭の文字列から検索しますので、途中で一致するものがあっても一致とはみなされません

re.match(パターン, テキスト)

一致すると、matchインスタンスを返します。
一致しなければ「None」が返ってきます。

search関数

searchは指定したパターンが最初に一致したものを調べる関数です。

こちらはmatchとは異なり、途中で一致するものがある場合は一致とみなされます。

re.search(パターン, テキスト)

一致すると、matchインスタンスを返します。
一致しなければ「None」が返ってきます。

findall関数

findallは指定したパターンに一致したもの全てを取得する関数です。

re.findall(パターン, テキスト)

一致すると、パターンと一致した文字列のリストを返します。(※matchインスタンスではないので注意)
一致しなければ「None」が返ってきます。

split関数

splitは指定したパターンと一致した部分で文字列を分割する関数です。

re.split(パターン, テキスト)

一致すると、分割された文字列のリストを返します。(※matchインスタンスではないので注意)
一致しなければ「None」が返ってきます。

検索した文字列を取り出すならfindall関数が便利だけど、検索したい文字列が含まれてるかを調べたいならsearch関数が良さそうですね

スポンサーリンク

パターンの作成

正規表現を利用する最大の問題は「どうやってパターンを作るか」です。

これにはパターンとして用意されている特殊な記号の役割を覚える必要があります。
パターンは検索する一般的なテキストに特殊な記号を組み合わせて作成します。

ではその基本的なものを以下に整理します。

最初はわけわかめじゃんと思うけど、わかめちゃんは仕方ないので少しづつ使ってみて慣れてくださいね

任意の文字列「.」

ドット(.)は、改行以外の任意の文字を表します。
例えば、”…”とすれば任意の3文字の文字列を示します。

先頭「^」と末尾「$」

単語の先頭「^」と末尾「$」を表します。
例えば、”^abc$”とすれば、「abc」は検索しますが、「zabc」「abcd」は検索しません。

繰り返し「*」「+」

記号の直前の文字を「*」は0回以上、「+」は1回以上を繰り返します
例えば、”a+”とすれば、「a」「aa」「aaa」全て検索します。

繰り返しの指定「{}」

{}は、記号の直前の文字を指定した数だけ繰り返します
例えば、”a{3}”とすれば「aaa」を検索します。
また、”a{2,4}”とすれば「aa」「aaa」「aaaa」というように”a”が2〜4個くり返されたものを検索します。

文字の集合「[]」

[]は、いくつかの文字の集合を表すものです。
[abc]は、a,b,cの3つの文字の集合でこれらのいずれかを示します。

例えば、”[abc]+”とすれば「aa」「aa」「abc」「cba」「abcba」…. というようにabcのいずれかの文字が1つ以上つながった文字列を全て検索します。

グループ「()」

()は、正規表現のグループを表すものです。
グループを指定することで、1つの正規表現パターンで複数の部分を取り出すことが出来ます。
(詳細の説明は後述の「後方参照の指定方法」を参照のこと)

複数の候補「|」

|記号は、グループの中にいくつかの候補を用意するのに使います。
例えば、”(abc|xyz)”とすると、「abc」「xyz」のいずれの文字列も検索します。

文字の範囲「-」

集合[]内で使われるもので、「aからcまでの文字」というように文字の範囲を表すのに使います。
例えば、”0-9″ならば半角の数字全てを示しますし、”a-z”とすればa〜zの全ての文字を示します。

それ以外を示す「^」

集合[]内で使われるもので、その後の記号以外を表します。
例えば、”[^a]”とすると、a以外の文字を示します。

エスケープ文字

“\○”というように\記号とその後の文字の組み合わせにより、特殊な働きを表す記号として使われるものです。
主なエスケープ文字を以下に示します。

文字役割
\A文字列の先頭を示す。
\d数字を示す。[0-9]と同じ
\D数字以外の文字を示す。[^0-9]と同じ
\s空白文字(スペースやタブ、改行文字など)を示す。
\S空白文字(スペースやタブ、改行文字など)以外を示す。
\w単語文字(英単語で利用される文字)を示す。[a-zA-z0-9_]と同じ
\W単語文字(英単語で利用される文字)以外を示す。
\z文字列の末尾を示す。

パターンに使われる記号はこの他にも多数ありますが、ここにあげたものが分かっていれば基本的なパターンは作れるようになりますので大丈夫っす

スポンサーリンク

正規表現による検索

では、Pythonのreモジュールを正規表現パターンを使って検索してみましょう。

名前とメールアドレスの文字列から正規表現を使って文字列の検索をします。

import re

data = '''
涼太 ryota@miyadate.com
百合子 yuriko@flower.jp
翔平 shohei@baseball.fr
美幸 miyuki@happy.org
'''

pattern = "[a-zA-Z]+@[a-zA-Z.]+"

match = re.match(pattern,data)
print(match)

match = re.search(pattern,data)
print(match)

relist = re.findall(pattern,data)
print(relist)

relist = re.split("[^a-zA-Z.@]+",data)
print(relist)

検索する文字列を「data」変数に格納しています。
また正規表現としてメールアドレス部分を検索するためのパターンを10行目に記載しています。

これで、match、search、findall、split関数を使って違いを見てみましょう。

実行結果は以下のとなります。

None
<re.Match object; span=(4, 22), match='ryota@miyadate.com'>
['ryota@miyadate.com', 'yuriko@flower.jp', 'shohei@baseball.fr', 'miyuki@happy.org']
['', 'ryota@miyadate.com', 'yuriko@flower.jp', 'shohei@baseball.fr', 'miyuki@happy.org', '']

matchは先頭から検索するので、先頭がメールアドレスのパターンに一致しないためNoneが返ってきます。

反対にsearchは文字列にメールアドレス部分が含まれていれば最初の検索結果を返すので「ryota@miyadate.com」のマッチ型インスタンスが返ってきています。

また、findallはメールアドレス部分を全て検索して文字列のリストで返ってきています。

splitはアルファベットとドットとアットマーク以外の文字列で分割していて、こちらは(前後の空白はありますが)メールアドレスで抜き出してリスト化しています。

スポンサーリンク

置換と後方参照

reモジュールは文字列の検索だけでなく、置換も正規表現に使えます

これには「sub」という関数を使います。

sub関数

re.sub(パターン, 置換文字列, 検索文字列

第2引数には置換後の文字列を設定し、第3引数に検索文字列を設定します。

基本的な使い方はこれまでの正規表現の関数と同じで、用意されたパターンを使って文字列を検索し、置換文字列に置換します。

戻り値は全ての文字が置換された状態の文字列が返ってきます。

スポンサーリンク

後方参照とは何か

単純に検索された文字列を決まった文字列に置き換えるならば上記の関数を使えば良いです。

ただし、検索でヒットした文字列を使って置換したい場合はどうでしょうか。

つまり正規表現のパターンで引っかかった文字列を置換文字列に指定するということっすね

そうそう、そういうことっす。

正規表現は決まりきった文字列ではなく、正規表現パターンに当てはまる文字列を検索するので、検索にヒットした文字列に応じて置換する文字列を作成したいこともあるかと思います。

その場合に「後方参照」と呼ばれる機能を使います。

これはパターンで探し出した文字列を置換文字列で利用するものです。

後方参照の指定方法

後方参照は先ほどパターン作成で説明した「グループ」を使います。

「()」を使い、パターンにグループを指定するとそのグループ部分の文字列を置換文字列側から使えるようになります。

グループは最初から順に番号が振られて、置換文字列側で「\番号」という形で指定することで、グループで検索された文字列を得ることが出来ます。

え、え、よく分からんのでもう少し分かりやすく説明して下さいな

まあ、あとでサンプルソース書いて説明しますけど。

例えば、先ほど使ったメールアドレスを検索するパターンで説明すると。

  • パターン:”([a-zA-Z]+)@([a-zA-Z.]+)”
  • 検索文字列:ryota@miyadate.com

この場合に、パターンを見ると「@」の前後に()をつけてグループ化していますので、パターンにヒットした以下の文字列が「\番号」で指定できます。

  • 第1グループ:ryota → \1
  • 第2グループ:miyadate.com → \2

として「\番号」として置換文字列に指定が可能となります。

置換は、後方参照を使うと検索した文字列を利用して置換できる。パターンの中にグループを用意すると後方参照できるようになる。

スポンサーリンク

文字列を置換する

では、具体的なサンプルを使って文字列の置換を見てみましょう。

今回は後方参照を使って置換をしてみます。

先ほどのメールアドレスの検索プログラムを少し変更してみましょう。

import re

data = '''
涼太 ryota@miyadate.com
百合子 yuriko@flower.jp
翔平 shohei@baseball.fr
美幸 miyuki@happy.org
'''

pattern = "([a-zA-Z]+)@([a-zA-Z]+)(.[a-zA-Z]+)"
replacement = r"(_@sample.com)"

relist = re.sub(pattern,replacement,data)
print(relist)

上記パターンで検索する文字列はメールアドレス「xxx@xxx.xxx」です。
さらにパターンを見てみると「@」前後の文字列と末尾のドメインをグループ化しています。

それぞれのグループ化と対応するパターンを先頭のSnowMan舘さまのメールアドレスで説明すると以下となります。

グループ1[a-zA-Z]+ryota\1
グループ2[a-zA-Z]+miyadate\2
グループ3.[a-zA-Z]+.com\3

上記の正規表現の検索にヒットしたデータを以下のパターンで置換しますので、置換後のメールアドレスは以下となります。

  • 置換文字列→r”(\1_\2@sample.com)”
  • 置換後の文字列→(ryota_miyadate@sample.com)

@前後の文字列をアンダースコア「_」でつなげて末尾に@sample.comをつけた文字列に置換しています。

また置換文字列の先頭に「r”(\1_\2@sample.com)”」と「r」を指定しています。

これは「r文字リテラル」といって「エスケープ文字をエスケープしない」ための指定です。

文字列リテラルでは「\」記号をそのまま書くとエスケープ文字と認識してもらえずそのまま「\」という文字列として認識されます。

なので文字列内にエスケープ文字「\」を記述する際は「\\」と2回書く必要があります。
ただし先頭に「r」をリテラルの前につけることで、わざわざ2回書くエスケープ処理をせずに「\」をそのまま扱えるようになります。

いちいち文字列内でエスケープ文字を2回書くのが面倒な場合は、文字列の先頭にr”…”と書くとエスケープ文字をそのまま処理してくれます

上記を実行した結果は以下となります。

涼太 (ryota_miyadate@sample.com)
百合子 (yuriko_flower@sample.com)
翔平 (shohei_baseball@sample.com)
美幸 (miyuki_happy@sample.com)
スポンサーリンク

まとめ

今回は、Pythonでの標準ライブラリである正規表現(reモジュール)の利用方法について学びました。

正規表現の利用はプログラムの作り方よりも「パターンの作り方」が重要です。

今回はごく簡単なメールアドレスの検索を行いましたが、URLとかHTMLのタグとか、郵便番号や電話番号など用途は多岐に渡るので、各用途に合わせたパターンはどうなるかを考えるのも良いかと思います。