正規表現を使うと、「文字列の中から自分で設定したパターンの文字列を探すこと」ができます。
正規表現が便利なことはわかっていても、いざ使おうとすると次のような問題に直面してしまうことも・・・。
- 検索したいとき
.search()
と.match()
どっち使うの? - 文字列だけほしいのに、
Matchオブジェクト
とかいう変な結果が出てきた! - マッチ箇所全部抽出したいのに、最初の一個しか抽出できない!
そこで、この記事ではPythonの正規表現ライブラリre
について、次の内容をわかりやすく解説しています。
- 検索メソッド(関数)の実行方法
- 各メソッドの紹介
- 検索結果の出力である
Matchオブジェクト
正規表現の検索メソッド(関数)一覧
正規表現パターンを使用した検索メソッド(関数)は主に次の2つに分類できます。
- 単独検索:文字列からマッチものを一つのみを抽出するもの(
.search()
など) - 全体検索:文字列からマッチするもの全てを抽出するもの(
.findall()
など)
この分類に応じて、メソッドを整理すると次のようになります。
分類 | メソッド名 | 詳細 |
---|---|---|
パターン検索(1つのみ) | .search() |
文字列中で最初にマッチしたもののMatchオブジェクト を返す |
.match() |
文字列の先頭がパターンにマッチしたら、Matchオブジェクト を返す |
|
.fullmatch() |
文字列全体がパターンにマッチしたら、Matchオブジェクト を返す |
|
パターン検索(マッチするもの全て) | .findall() |
文字列内でパターンマッチするもの全てを[リスト] で返す |
.finditer() |
findall()の結果をiterator で返す |
この記事では、各検索メソッドについて詳しく解説していきます。
Pythonでの正規表現パターンの生成方法については、次の記事で詳しく解説しています。
正規表現パターンの作り方(文字の繰り返し、文字条件の指定、グループ化)がわからない方は、こちらも参考にしてください。
検索メソッド実行方法|re.compileありなし
各メソッドの詳しい内容の前に、まずは検索メソッド(関数)はそもそもどうやって実行するのか確認してみましょう。
正規表現のメソッド(関数)には、次の2つの実行方法があります。
- 使用のたびにパターンを設定して実行するパターン
re.メソッド名("正規表現パターン", その他引数)
- 初めに正規表現パターンをコンパイルして実行するパターン
p = re.compile(正規表現パターン)
p.メソッド名()
どちらも結果は同じですが、基本次項なので、実行例を見て使い方の違いを確認しておきましょう。
まずは、re.メソッド名()
で実行する例を見てみます。
import re
text = "1月1日は元旦です。"
m = re.search("\d月\d日", text) # ←実行時に正規表現パターンを与えている
print(m)
# <re.Match object; span=(0, 4), match='1月1日'>
次に正規表現パターンをコンパイルする例を見てみます。
text = "1月1日は元旦です。"
p = re.compile("\d月\d日") # 先に正規表現パターンをcompileして、正規表現オブジェクト生成
m = p.search(text) # 正規表現オブジェクトでパターンは設定済み ⇒ メソッド実行時に再指定不要
print(m)
# <re.Match object; span=(0, 4), match='1月1日'>
一度コンパイルした、正規表現オブジェクトは再利用可能です。
そのため、プログラム内で同じ正規表現パターン使用する場合は、コンパイルした方が良い場合が多いです。
text = "1月1日は元旦です。"
text2 = "5月5日は子供の日です。"
texts = [text, text2]
p = re.compile("\d月\d日") # パターンをcompileして、正規表現オブジェクト生成
for t in texts:
m = p.search(t) # 正規表現オブジェクトでパターンは設定済み ⇒ メソッド実行時に再指定不要
print(m)
# <re.Match object; span=(0, 4), match='1月1日'>
# <re.Match object; span=(0, 4), match='5月5日'>
まとめると次のように使い分ければOKです(個人的には、使い分けにそれほど拘る必要はないと思いますが…)。
- プログラムの中で繰り返し同じパターンを使う場合 ⇒ コンパイル
p = re.compile(正規表現パターン)
p.メソッド名()
- 繰り返し使用しない場合 ⇒使用のたびにパターン設定
re.メソッド名(正規表現パターン)
re.メソッド名()
、p.メソッド名()
の使い分けがわかったところで、以下で各メソッドのより具体的な使用方法を紹介していきます。
以下では、re.メソッド名()
の形式で解説しますが、p.メソッド名()
でも使用方法はほとんど同じです。
パターン検索(1つのみ)
まずは、パターンに一致した箇所を「1つだけ」抽出できるメソッドを解説していきます。
分類 | メソッド名 | 詳細 |
---|---|---|
パターン検索(1つのみ) | .search() |
文字列中で最初にパターンにマッチした箇所のMatchオブジェクト を返す |
.match() |
文字列の先頭がパターンにマッチしたら、Matchオブジェクト を返す |
|
.fullmatch() |
文字列全体がパターンにマッチしたら、Matchオブジェクト を返す |
多くの場合は、.search()
メソッドを使用して、マッチ個所を検索することが多いと思います。
.match()
、.fullmatch()
も状況に応じて、使用することがあるので、内容をチェックしていきましょう。
パターン検索(1つのみ)|.search()
おそらくpythonのパターン検索の中で使用頻度が最も高いメソッドだと思います。
.search()
は、文字列中で「最初に」パターンにマッチした箇所のMatchオブジェクト
を返します。
まずはサンプルコードで.search()
メソッドの挙動を確認してみましょう。
text = "4月1日は焼肉を食べた。"
re.search("\d月\d日", text)
# <re.Match object; span=(0, 4), match='4月1日'>
"\d月\d日"
の正規表現パターンにマッチした'4月1日'
に対応するMatchオブジェクト
が取得できていますね。
.search()
メソッドのポイントは、「最初に」パターンにマッチした箇所のMatchオブジェクト
を返す点です。
マッチする箇所が2つあったとしても、最初のマッチ個所しか返してくれません。
text = "4月1日は焼肉を食べて、5月1日は寿司を食べた。"
re.search("\d月\d日", text)
# <re.Match object; span=(0, 4), match='4月1日'>
1つ目のマッチ個所'4月1日'
を返しましたが、2つ目のマッチ個所'5月1日'
は返してくれません。
2つ目以降のマッチ個所を探したい場合は、.findall()
メソッド等を使用します。
これについては、後述のパターン検索(マッチするもの全て)で解説します。
パターン検索(先頭1つのみ)|.match()
.search()
に似たパターン検索関数に、.match()
があります。
これらの違いは次の通りです。
.match()
:文字列の「先頭」がパターンにマッチしなければならない.search()
:文字列の途中でもパターンにマッチすればOK
以下で、先頭にパターンがある場合とそうでない場合の.match()
の挙動の違いを確認してみましょう。
text = "4月1日は焼肉を食べた。"
re.match("\d月\d日", text)
# <re.Match object; span=(0, 4), match='4月1日'>
パターンが先頭にあるので、確かにMatchオブジェクト
を返しています。
次にパターンがテキストの途中にある場合の実行例を見てみます。
text = "私は4月1日は焼肉を食べた。"
re.match("\d月\d日", text)
# None
文字列の先頭でマッチしないので、None
が返されています。
.match()と特殊文字^の違い
.match()
は、文字列の先頭でマッチするか判定する関数です。
一方、正規表現パターン自体にも文字列の先頭を指定する文字列"^"
があります。
"^"
|パターンが先頭でマッチすることを指定する正規表現の特殊文字- 使用方法:パターンの先頭に
"^"
を付ける - 使用例:
"^\d月/d日"
- 使用方法:パターンの先頭に
例として、"^"
の使い方を見てみましょう。
text = "私は4月1日は焼肉を食べた。"
re.search("^\d月\d日", text) #^でパターンがテキストの先頭にあることを指定
# None
#パターンが先頭にないのでマッチしない
text = "4月1日は焼肉を食べた。"
re.search("^\d月\d日", text) #^でパターンがテキストの先頭にあることを指定
# <re.Match object; span=(0, 4), match='4月1日'>
# パターンがテキストの先頭にあるのでマッチ
これを見ると、.match()
関数と特殊文字"^"
の機能が重複しているように見えます。
しかし、この2つは改行のあるテキストでre.MULTILINE
を指定した場合に挙動が異なります。
.match()
関数|文字列の先頭のみでマッチ判定(改行後のパターン一致はマッチとしない)- 特殊文字
"^"
|各行の先頭でマッチするか判定
サンプルコードで挙動の違いを確認してみましょう。
text = """私の食事について話します。
5月1日は寿司を食べた。
6月1日はピザを食べた。
"""
re.match("\d月\d日", text, re.MULTILINE)
# None
# 文字列の先頭にマッチしないので、None
text = """私の食事について話します。
5月1日は寿司を食べた。
6月1日はピザを食べた。
"""
re.search("^\d月\d日", text, re.MULTILINE)
# <re.Match object; span=(15, 19), match='5月1日'>
# 行の先頭でマッチするか判定⇒5月1日にマッチ
re.MULTILINE``.match()
関数と特殊文字"^"
で挙動が異なるので、適宜使い分けましょう。
パターン検索(全体一致のみ)|.fullmatch()
.fullmatch()
は、文字列全体がパターンに一致している場合にMatchオブジェクト
を返します。
text = "4月1日"
re.fullmatch("\d月\d日", text)
# <re.Match object; span=(0, 4), match='4月1日'>
文字列の前後に余計な文字が入っているとマッチしません。
text = "4月1日は焼肉を食べた。"
re.fullmatch("\d月\d日", text)
# None
Matchオブジェクトの中身
.search()
や.match()
メソッドで、正規表現パターンがマッチする結果としてMatchオブジェクト
が返されます。
以下で、Matchオブジェクト
の内容について解説していきます。
Matchオブジェクト
は、次の内容をまとめたオブジェクトと考えるとわかりやすいです。
- マッチした文字列
- マッチの位置
- 正規表現パターン内の各グループの内容
とりあえず、マッチした文字列だけ抽出したい場合は、次のメソッドだけ覚えておけばOKです。
m.group()
->"マッチした文字列"
を出力
text = "私は4月1日は焼肉を食べた。"
m = re.search("\d月\d日", text)
print(m) # 出力内容はMatchオブジェクトの概要
# <re.Match object; span=(2, 6), match='4月1日'>
print(m.group()) # マッチした文字列のみ出力
# '4月1日'
その他のメソッドで、使用頻度が比較的高いものについて、以下で紹介します。
Matchオブジェクトの便利なメソッド一覧
Matchオブジェクトで比較的使用頻度の高いメソッド一覧は次の通りです。
分類 | 出力内容 | メソッド名 | 引数など |
---|---|---|---|
マッチ文字列出力 | マッチ文字列全体 | .group() |
引数省略 or 0 でマッチ文字列全体を出力 |
特定グループの内容 | .group() |
グループ番号 or "グループ名" を引数に渡すと、そのグループ内容のみ出力 |
|
グループ内容のリスト | .groups() |
引数不要(パターンにグループがないと空) | |
マッチ位置出力 | (開始,終了) 形式のタプル |
.span() |
引数省略でマッチ文字列全体の位置、引数でグループ指定も可能 |
開始位置のみ | .start() |
〃 | |
終了位置のみ | .end() |
〃 |
マッチ文字列・グループ内容出力|.group()
Matchオブジェクト,m
からマッチした文字列全体を出力する場合は、.group()メソッドを実行します。
引数を省略するか、0
を与えると、マッチした文字列全体を出力します。
m.group()
m.group(0)
簡単な実行例を見てみましょう。
text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)
m.group() # マッチした文字列のみ出力
# '4月1日'
この例では、正規表現パターン内に、<Month>
グループと、<Day>
グループを設定しています。
しかし、グループ関係なしにマッチ文字列全体を出力していますね。
各グループの内容を個別に参照したい場合には、.group()
メソッドにグループ番号
またはグループ名
を渡します。
text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)
# グループ番号を指定する例
m.group(1)
# '4月'
m.group(2)
# '1日'
# グループ名を指定する例
m.group("Month")
# '4月'
m.group("Day")
# '1日'
引数に複数グループの番号、名前を渡して、複数同時に出力することも可能です。
text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)
# グループ番号を指定する例
m.group(1, 2)
# ('4月', '1日')
# グループ名を指定する例
m.group("Month", "Day")
# ('4月', '1日')
ただし、全てのグループ内容を出力する場合には、次に紹介する.groups()
の方が便利です。
グループ内容を全て出力|.groups()
正規表現パターンがグループを持つ場合に、グループの内容を一覧で取得したい場合は.groups()
メソッドを使用します。
text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)
m.groups()
# ('4月', '1日')
グループが設定されていない場合には、空のタプルが返されます。
text = "私は4月1日は焼肉を食べた。"
m = re.search("\d月\d日", text)
m.groups()
# ()
マッチ位置の出力|.span()
正規表現パターンが、文字列のどの位置にマッチしたかを出力する際には、次のメソッドを使用します。
分類 | 出力内容 | メソッド名 |
---|---|---|
マッチ位置出力 | (開始,終了) 形式のタプル |
.span() |
開始位置のみ | .start() |
|
終了位置のみ | .end() |
引数を省略すると、マッチ文字列全体の開始位置、終了位置を取得できます。
text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)
# 開始位置、終了位置のタプル取得
m.span()
# (2, 6)
# 開始位置のみ
m.start()
# 2
# 終了位置のみ
m.end()
# 6
グループ番号またはグループ名を指定すれば、特定のグループの開始・終了位置を出力することもできます。
text = "私は4月1日は焼肉を食べた。"
m = re.search("(?P<Month>\d月)(?P<Day>\d日)", text)
# 開始位置、終了位置のタプル取得
m.span(2)
# (4, 6)
# 開始位置のみ
m.start(2)
# 4
# 終了位置のみ(グループ名で指定)
m.end("Day")
# 6
パターン検索(マッチするもの全て)
.search()
は基本的なパターン検索の関数ですが、文字列の中で一番最初にマッチした箇所しか返してくれません。
マッチするもの全てを見つけたい場合には、次の3つの方法があります。
分類 | メソッド名/方法 | 詳細 |
---|---|---|
パターン検索(マッチするもの全て) | .findall() |
文字列内でマッチするもの全てを[リスト] で返す |
.finditer() |
Matchオブジェクト を順番に生成するiterator で返す |
|
.search() とwhile文 の組み合わせ |
while文 内で検索範囲を調整 |
マッチ箇所全て抽出|findall()
.findall()
は、文字列内でパターンにマッチした部分を[リスト]
にして返します。
text = """私は4月1日は焼肉を食べて、
5月1日は寿司を食べて、
6月1日はピザを食べた。
"""
re.findall("\d月\d日", text)
# ['4月1日', '5月1日', '6月1日']
一致箇所を全て抽出したいときには、かなり便利な関数ですね。
.findall()
で覚えておきたいものとして、次の挙動があります。
- 正規表現パターンでグループを指定すると、グループの内容をタプルにしたリストを返す
サンプルコードで、正規表現パターン内にグループを指定した場合の挙動を確認してみましょう。
text = """私は4月1日は焼肉を食べて、
5月1日は寿司を食べて、
6月1日はピザを食べた。
"""
re.findall("(\d月\d日)は(\w+)を", text)
# [('4月1日', '焼肉'), ('5月1日', '寿司'), ('6月1日', 'ピザ')]
必要な箇所を抜き出すのに非常に便利ですね。
マッチ箇所イテレータで抽出|finditer()
.findall()
は、一致箇所を全て[リスト]
で抽出する際に便利ですが、次のようなときに不便です。
Matchオブジェクト
として取得したい- 全てを
[リスト]
で取得したいわけではない- 特定の回数だけでいい場合や、文字列の途中までの検索で良い場合など
そういった場合は、.finditer()
関数を使用する便利です。
.finditer()
関数は、Matchオブジェクト
を一つずつ出力するイテレータを取得できます。
イテレータを取得した後は、for文
で回すなりして必要な処理をしましょう。
text = """私は4月1日は焼肉を食べて、
5月1日は寿司を食べて、
6月1日はピザを食べた。
"""
match_iter = re.finditer("\d月\d日", text)
for t in match_iter:
print(t)
# <re.Match object; span=(2, 6), match='4月1日'>
# <re.Match object; span=(15, 19), match='5月1日'>
# <re.Match object; span=(28, 32), match='6月1日'>
リストとして、Matchオブジェクト
をまとめたい場合にはlist()
を適用すればOKです。
text = """私は4月1日は焼肉を食べて、
5月1日は寿司を食べて、
6月1日はピザを食べた。
"""
match_iter = re.finditer("\d月\d日", text)
match_list = list(match_iter)
match_list
# [<re.Match object; span=(2, 6), match='4月1日'>,
# <re.Match object; span=(15, 19), match='5月1日'>,
# <re.Match object; span=(28, 32), match='6月1日'>]
.search()とfor文の組み合わせ
.search()
とfor文
を組み合わせて、マッチ個所全てを抽出することも可能です。
Matchオブジェクト
の位置を利用して、文字列内の検索範囲をずらしていきます。
text = """私は4月1日は焼肉を食べて、
5月1日は寿司を食べて、
6月1日はピザを食べた。
"""
pos = 0 # 検索範囲の初期値(0は文字列の先頭)
match_list = [] # 結果のリストの準備
while m := re.search("\d月\d日", text[pos:]): # 文字列内のpos以降でマッチする限りループ
match_list.append(m) # Matchオブジェクトを結果のリストに格納
pos+=m.end() # マッチした文字列の後尾にposの値を変更
match_list
# [<re.Match object; span=(2, 6), match='4月1日'>,
# <re.Match object; span=(9, 13), match='5月1日'>,
# <re.Match object; span=(9, 13), match='6月1日'>]
何らかの理由で.finditer()
を使わずに処理したい場合には、この方法もありですね。
正規表現の関連記事
今回は正規表現での検索方法について解説しました。
正規表現関連のその他の記事は次の通りです。
正規表現パターンの生成方法
Pythonでの正規表現のパターンの生成方法については、次の記事で詳しく解説しています。
文字の繰り返し、文字条件の指定、グループ化がよくわからないという方は、こちらも参考にしてください。
正規表現応用例
正規表現は実際に使ってみないとなかなか身につかないものです。
正規表現の応用例は、次の記事で使用例を紹介しているので、参考にしてみてください!