[解決!Python]re.search/re.match関数と正規表現を使って文字列から部分文字列を抽出するには:解決!Python
Pythonの正規表現モジュール(re)が提供するre.search/re.match関数を使って、文字列からパターンにマッチした部分を抽出する方法を紹介する。re.Matchオブジェクトも簡単に取り上げる。
* 本稿は2021年3月9日に公開された記事をPython 3.12.1で動作確認したものです(確認日:2024年1月8日)。
import re
s = 'id: deep, mail: deep@foo.com, tel: 03-0123-4567'
# re.search関数は文字列にパターンとマッチする部分があるかを調べる
m = re.search('tel: [-\d]+', s)
print(m) # <re.Match object; span=(30, 47), match='tel: 03-0123-4567'>
r = m.group() # re.Matchオブジェクトのgroupメソッドでマッチ全体を抽出
print(r) # tel: 03-0123-4567
# re.match関数は文字列の先頭とパターンがマッチするかを調べる
m = re.match('tel: [-\d]+', s) # 「tel: 03-……」は先頭にはないので
print(m) # これはNoneとなる
m = re.match('\w+: [-\w@.]+', s) # 文字列先頭が「\w+: [-\w@.]+」にマッチするか
print(m) # <re.Match object; span=(0, 8), match='id: deep'>
r = m.group()
print(r) # id: deep
# re.search関数が返すのは最初にマッチした部分を表すre.Matchオブジェクト
m = re.search('\w+: [-\w@.]+', s)
print(m) # <re.Match object; span=(0, 8), match='id: deep'>
print(m.group()) # id: deep
# 全てのre.Matchオブジェクトを取得するにはre.finditer関数を使う
m_iter = re.finditer('\w+: [-\w@.]+', s)
for m in m_iter:
print(m.group())
# 出力結果:
# id: deep
# mail: deep@foo.com
# tel: 03-0123-4567
# re.Matchオブジェクトのメソッドを使って抽出する文字列をカスタマイズする
m = re.match('id: \w+', s)
start = m.start() # マッチ全体の開始位置を取得
end = m.end() # マッチ全体の終了位置を取得
print(f'start: {start}, end: {end}') # start: 0, end: 8
print(f'span of match: {s[start:end]}') # span of match: id: deep
start, end = m.span() # マッチ全体の開始位置と終了位置を取得
print(f'span of match: {s[start:end]}') # span of match: id: deep
m = re.match('id: (\w+)', s) # グループを指定
r = m.group() # マッチ全体を取得
print(r) # id: deep
r = m.group(1) # 最初のグループを取得
print(r) # deep
re.search関数とre.match関数
Pythonが標準で提供する正規表現モジュール(re)には、文字列から指定したパターンにマッチする部分を抽出するのに使える関数が幾つか用意されている。本稿ではその中からre.search関数とre.match関数を主に取り上げる。なお、re.findall関数を使って、文字列から部分文字列を抽出する方法については「[解決!Python]re.findall関数と正規表現を使って文字列から部分文字列を抽出するには」を参照されたい。
re.search関数とre.match関数はいずれも指定したパターンにマッチする部分が文字列にあるかを調べて、マッチしたらそれを表すre.Matchオブジェクトを返す。ただし、re.search関数はマッチする最初の場所を文字列全体から探すのに対して、re.match関数は文字列先頭から探す(文字列先頭とパターンがマッチするかどうかを調べる)点が異なる。文字列が特定のパターンを含んでいるかどうか調べて該当する部分を抽出するならre.search関数を使い、文字列の先頭が特定のパターンとマッチするかを調べるならre.match関数を使うようにするとよい。
以下に例を示す。
import re
s = 'id: deep, mail: deep@foo.com, tel: 03-0123-4567'
# re.search関数は文字列にパターンとマッチする部分があるかを調べる
m = re.search('tel: [-\d]+', s)
print(m) # <re.Match object; span=(30, 47), match='tel: 03-0123-4567'>
r = m.group() # re.Matchオブジェクトのgroupメソッドでマッチ全体を抽出
print(r) # tel: 03-0123-4567
# re.match関数は先頭から調べるので以下はNoneが返される
m = re.match('tel: [-\d]+', s)
print(m) # None
この例では、re.search関数とre.match関数に同じパターン「tel: [-\d]+」を渡している。これは「tel: 」に続けて連続する数字とマイナス記号を表す。検索対象の文字列は「'id: deep, mail: deep@foo.com, tel: 03-0123-4567'」なので、「tel: 03-1234-5678」はこれにマッチする。マッチした部分の文字列はre.Matchオブジェクトのgroupメソッドで抽出できる。
re.search関数は文字列全体で最初にマッチする部分を探すので、これを見つけられる。しかし、re.match関数は文字列先頭とパターンを比較するので、Noneが返される。
以下は、re.match関数で先頭と「\w+: [-\w@.]+」というパターンがマッチする例だ。
m = re.match('\w+: [-\w@.]+', s)
print(m) # <re.Match object; span=(0, 8), match='id: deep'>
r = m.group()
print(r) # id: deep
このパターン「\w+: [-\w@.]+」は、「連続する英数字: 連続するマイナス記号か英数字か@か.」を表すので、「id: deep」「mail: deep@foo.com」「tel: 03-1234-5678」にマッチする。ただし、re.match関数は文字列の先頭部分がパターンにマッチするかどうかを調べるので、これが返送するのはあくまでも「id: deep」を表すre.Matchオブジェクトとなる。
同じパターンをre.search関数に渡すと、やはり「id: deep」を表すre.Matchオブジェクトが返される。
m = re.search('\w+: [-\w@.]+', s)
print(m) # <re.Match object; span=(0, 8), match='id: deep'>
r = m.group()
print(r) # id: deep
re.search関数ではある文字列の中にマッチする箇所が複数あっても、最初にマッチする部分を表すre.Matchオブジェクトしか返されないことには注意しよう。複数のマッチをre.search関数やre.match関数と同じく、re.Matchオブジェクトの形で取得するにはre.finditer関数を使用する必要がある(複数のマッチを文字列として取得するにはre.findall関数を使用できる)。
以下に例を示す。
m_iter = re.finditer('\w+: [-\w@.]+', s)
for m in m_iter:
print(m.group())
# 出力結果:
# id: deep
# mail: deep@foo.com
# tel: 03-0123-4567
この例では先ほどと同じパターンを、re.finditer関数に渡している。戻り値はマッチした部分に対応するre.Matchオブジェクトを反復するイテレータとなることには注意しておこう。
抽出する文字列のカスタマイズ
これまでに見てきたように、マッチした部分の文字列を抽出するにはre.Matchオブジェクトのgroupメソッドを使うのが簡単だが、正規表現でグループを指定して、その部分だけを抽出したいこともあるだろう。こうしたときには、groupメソッドに引数を渡したり、re.Matchオブジェクトの他のメソッドを使って、マッチした箇所のインデックスを取得したりしてもよい。
ここでは詳しい説明は省略するが、re.Matchオブジェクトには以下のようなメソッドがある(一部を抜粋)。
- startメソッド:マッチの開始位置を取得。引数に整数を指定すると対応するグループの開始位置を取得
- endメソッド:マッチの終了位置を取得。引数に整数を指定すると対応するグループの終了位置を取得
- spanメソッド:マッチの開始位置と終了位置をタプルで取得。引数に整数を指定すると対応するグループの開始位置と終了位置をタプルで取得
- groupメソッド:マッチした部分を文字列として取得。引数に整数を1つ指定すると、対応するグループを文字列として取得。整数を複数指定すると、対応するグループの文字列を格納するタプルを取得
- groupsメソッド:全てのグループの文字列を格納するタプルを取得
この他にもさまざまなメソッドや属性があるが、それらについてはPythonの公式ドキュメントにある「マッチオブジェクト」を参照されたい。
以下に例を示す。
m = re.match('id: \w+', s)
start = m.start() # マッチ全体の開始位置を取得
end = m.end() # マッチ全体の終了位置を取得
print(f'start: {start}, end: {end}') # start: 0, end: 8
print(f'span of match: {s[start:end]}') # span of match: id: deep
start, end = m.span() # マッチ全体の開始位置と終了位置を取得
print(f'span of match: {s[start:end]}') # span of match: id: deep
最初の例では、start/endメソッドを使って、マッチ全体の開始位置と終了位置を取得し、その値を使って文字列のスライスとしてマッチした部分を取り出している。次の例では開始位置と終了位置の取得にspanメソッドを使っている。どちらの例でも、メソッドには引数を与えていないので、マッチした全体が得られている。
以下は正規表現にグループを指定している場合の例だ。
m = re.match('id: (\w+)', s) # グループを指定
r = m.group() # マッチ全体を取得
print(r) # id: deep
r = m.group(1) # 最初のグループを取得
print(r) # deep
この例では「(\w+)」をいうグループ1つだけ正規表現中で指定している。groupメソッドを引数なしで呼び出した結果は、これまでと同様にマッチ全体となっているが、groupメソッドに「1」を渡すと、グループ「(\w+)」にマッチした「deep」だけが抽出できている点に注目されたい。なお、上で見たstart/end/spanの各メソッドでもグループに対応する整数を引数に指定すると、そのグループの開始位置や終了位置を取得できる。
m = re.match('id: (\w+)', s) # グループを指定
r = m.group(1) # 最初のグループを取得
print(r) # deep
start, end = m.span(1)
print(s[start:end]) # deep
最後に正規表現に複数のグループが含まれている場合の例を示す。これについてはコードの説明は不要だろう。
m = re.match('(\w+): (\w+)', s) # 複数のグループがある場合
r = m.group() # マッチ全体を取得
print(r) # id: deep
r = m.group(2) # 2つ目のグループを取得
print(r) # deep
r = m.groups() # 全てのグループを取得
print(r) # ('id', 'deep')
Copyright© Digital Advantage Corp. All Rights Reserved.