re

【Python】正規表現モジュール re の基本的な使い方

【Python】正規表現モジュール re の基本的な使い方
naoki-hn

Pythonで正規表現(regular expression)を抽出する際に使用する re モジュールの基本的な使い方について解説します。

正規表現(Regular Expression)

正規表現(Regular Expression)は、あいまいな文字列パターンを表現するための表現方法です。Python では、正規表現を扱うために re モジュールが用意されており、文字列に含まれる正規表現パターンを抽出することができます。

任意にパターンに一致するものを探す方法としてワイルドカード(「*」(複数文字)や「?」(任意1文字)等)の表現を使って、「*.txt」のようにWindowsエクスプローラーでファイル検索する人もいるかと思います。正規表現はワイルドカードよりも、さらに複雑なパターンを表現することが可能です。

例えば、電話番号であれば、「\d{2,4}-\d{2,4}-\d{4}」のように正規表現であらわせます。\d が10進数数値を表しており、{2,4} は直前の文字が 2~4 回出現するパターンにマッチします。{4} は直前の文字が 4 回出現するパターンということを意味します。この例で直前文字と言っているのは \d の 10 進数数値の事です。

正規表現は、色々な業務システムで利用されており、テキストマイニング等の自然言語処理システムでも必要な技術のため基本を理解しておきましょう。

正規表現は、それだけで本があるぐらい奥が深いので細かな説明は割愛します。興味がある方は、関連書籍や関連Webページを検索してみてください。また、自分が記載した正規表現で対象文字列を抽出できるかを確認できるサイトも結構あり、「正規表現チェッカー」などで検索すれば出てきますのでうまく活用してみてください。

この記事では、Python の re モジュールの基本的な使い方を説明します。

re モジュール

正規表現を Python の re モジュールで抽出する方法を例を用いて紹介していきます。

正規表現パターン抽出の基本

正規表現モジュールの re の基本的な使い方は以下の手順になります。

  1. compile 関数で正規表現パターンを作成する。
  2. 正規表現パターンに文字列を指定して判定する。判定には searchmatch といったメソッドを使用する。

search メソッドを使用するか、match メソッドを使用するかで少し挙動が違います。簡単に記載すると以下の通りです。

  • search:任意位置で一致する場合を抽出したい
  • match:先頭から一致する場合を抽出したい

以下で例を見ながら searchmatch それぞれの使用方法を紹介します。

search メソッドを用いた正規表現の抽出

search メソッドを用いた正規表現の抽出の例を紹介します。

import re

text1 = "電話番号は、090-1111-2222です。"
text2 = "電話番号は、0123-45-6789です。"

# 正規表現をcompileで準備する
# \を使うのでrのraw文字列として引数に設定する
ptrn = re.compile(r"(\d{2,4})-(\d{2,4})-(\d{4})")

# 文字列を検索し、結果を表示する
result1 = ptrn.search(text1)
print(type(result1))
if result1:
    # 引数なしはヒットした全体を表示。result.group(0)でも同じ
    print(result1.group())
    # 1番目の部分文字列
    print(result1.group(1))
    # 2番目の部分文字列
    print(result1.group(2))
    # 3番目の部分文字列
    print(result1.group(3))
else:
    print("一致なし")

print("=====")
# Python 3.8以降であればセイウチ演算子を使ってもよい
if result2 := ptrn.search(text2):
    print(result2.group())
    print(result2.group(1))
    print(result2.group(2))
    print(result2.group(3))
else:
    print("一致なし")
【実行結果】
<class 're.Match'>
090-1111-2222
090
1111
2222
=====
0123-45-6789
0123
45
6789

以降でポイントを順に説明していきます。

まず、正規表現の抽出を行うためには re モジュールのインポートする必要があります。次に、compile 関数で抽出したい正規表現パターンを以下のように設定します。

# 正規表現をcompileで準備する
# \を使うのでrのraw文字列として引数に設定する
ptrn = re.compile(r"(\d{2,4})-(\d{2,4})-(\d{4})")

正規表現パターンは性質上「\」を多く使います。文字列を使うときのエスケープシーケンスとぶつかってしまうことを避けるため、raw 文字列(r"~")を使用するとよいでしょう。

result1 = ptrn.search(text1)

次に、用意したパターンの serch メソッドに抽出対象としたい文字列を渡します。抽出した文字列を表示したい場合には、group メソッドを使用して抽出文字列を取得することができます。

if result1:
    # 引数なしはヒットした全体を表示。result.group(0)でも同じ
    print(result1.group())
    # 1番目の部分文字列
    print(result1.group(1))
    # 2番目の部分文字列
    print(result1.group(2))
    # 3番目の部分文字列
    print(result1.group(3))
else:
    print("一致なし")

search メソッドの返却値の型を表示していますが、「re.Match」クラスのオブジェクトになっています。もし、検索結果で正規表現に一致するものがなかった場合は None が返ってきますので、抽出結果を確認する前に if 文で判定をしています。

re.Matchgroup メソッドを使うことで抽出された文字列を参照できます。引数がなし又は 0 の場合は、正規表現に一致した文字列全体を取得できます。

引数に 1 以上を指定すると、正規表現中のグループであるサブマッチ文字列を取得できます。ここでサブマッチ文字列というのは、正規表現の compile の際に () でくくった単位の事です。今回の例では「r"(\d{2,4})-(\d{2,4})-(\d{4})"」と () の単位が 3 つあるので1, 2, 3という引数で各グループに該当した文字列を取得できます。

なお、if 文の部分ですが、Python 3.8 以降を使用している場合であれば、セイウチ演算子用いて以下のようにまとめてしまうのがシンプルです。text2 の抽出の方はセイウチ演算子を使った例で記載しています。

if result2 := ptrn.search(text2):

その他にも startendspan といったメソッドで抽出されたグループごとの開始、終了位置を取得するといったこともできます。re の公式ドキュメントのこちらを参照してください。

次は、search に似た match メソッドについて紹介します。

match メソッドを使用した正規表現の抽出

search によく似たメソッドとして、match メソッドがあります。

この match メソッドの特徴は、「文字列の先頭からのみをマッチ対象とする」という点です。以下の例で見てみましょう。

import re

text1 = "090-1111-2222が電話番号です。"
text2 = "電話番号は、0123-45-6789です。"

ptrn = re.compile(r"(\d{2,4})-(\d{2,4})-(\d{4})")

result1 = ptrn.match(text1)
if result1:
    print(result1.group())
    print(result1.group(1))
    print(result1.group(2))
    print(result1.group(3))
else:
    print("一致なし")

print("=====")
if result2 := ptrn.match(text2):
    print(result2.group())
    print(result2.group(1))
    print(result2.group(2))
    print(result2.group(3))
else:
    print("一致なし")
【実行結果】
090-1111-2222
090
1111
2222
=====
一致なし

内容は上記の search と同様の例を match に置き換えた例です。text1 は電話番号から文字列が始まっており、text2 は文字列の途中に電話番号が出てきています。

search メソッドであればいずれも抽出できますが、match メソッドだと文字列中に電話番号が出現する text2 は抽出することができません。

先頭から抽出したい場合は、match を使用すると覚えておきましょう。

正規表現に一致する全ての文字列を取得する

searchmatch メソッドは、どちらも最初にマッチした文字列を一つ返すだけです。しかし、多くの場合は対象文字列に含まれる正規表現に一致する文字列を全て取得したいケースが多いかと思います。

対象の文字列から正規表現に一致する文字列を全て取得したい場合は、findall メソッドまたは finditer メソッドを使用します。どちらも正規表現に一致する文字列をすべて取得する点で同じですが、以下のような違いがあります。

  • findall:正規表現に一致する文字列をリストで取得
  • finditer:正規表現に一致する文字列を re.Match オブジェクトで取得

以下で例を見ながら findallfinditer それぞれの使用方法を紹介します。

findall メソッドを使用する方法

findall メソッドを使用した正規表現の取得方法について、例を用いて見ていきます。

import re

text = (
    "私の電話は0123-45-6789で、携帯は090-1111-2222です。"
    "Aさんの電話は9876-54-3210で、携帯は080-3333-4444です。"
)

# パターンを設定(グループ設定)
ptrn1 = re.compile(r"(\d{2,4})-(\d{2,4})-(\d{4})")
# マッチする文字列を全て検索
match_texts = ptrn1.findall(text)
print(type(match_texts))
for match_text in match_texts:
    print(match_text)

print("=====")
# パターンを設定(グループなし)
ptrn2 = re.compile(r"\d{2,4}-\d{2,4}-\d{4}")
# マッチする文字列を全て検索
match_texts = ptrn2.findall(text)
print(type(match_texts))
for match_text in match_texts:
    print(match_text)
【実行結果】
<class 'list'>
('0123', '45', '6789')
('090', '1111', '2222')
('9876', '54', '3210')
('080', '3333', '4444')
=====
<class 'list'>
0123-45-6789
090-1111-2222
9876-54-3210
080-3333-4444

正規表現のパターンを compile 関数で準備するところは、searchmatch メソッド使用時と同じです。

作成したパターンで findall メソッドを以下のように実行することで、正規表現に一致するものをリスト(list)形式で取得することができます。

match_texts = ptrn1.findall(text)

findall メソッドは、正規表現パターンでグループを表す () を使用した場合には、グループ毎に抽出した文字列をタプル(tuple)にまとめた形で返却します。

グループを表す () がない場合は、正規表現に一致する文字列全体がリストとして取得できます。

finditer メソッドを使用する方法

上記で見てきた findall メソッドで取得されるのはリスト(list)形式です。

searchmatch メソッドでは、re.Match のオブジェクトが返却されたため group メソッドによりサブマッチ文字列を取得できました。finditer メソッドを用いると正規表現に一致する全ての文字列を re.Match オブジェクトで取得することができます。

正確にはre.Match オブジェクトのイテレータを返すので、for 文で順に処理することができます。finditer メソッドを使用した正規表現の取得方法について、以下例を用いて見ていきましょう。

import re

text = (
    "私の電話は0123-45-6789で、携帯は090-1111-2222です。"
    "Aさんの電話は9876-54-3210で、携帯は080-3333-4444です。"
)

# パターンを設定
ptrn1 = re.compile(r"(\d{2,4})-(\d{2,4})-(\d{4})")
# マッチする文字列を全て検索
match_texts = ptrn1.finditer(text)
print(type(match_texts))
for match_text in match_texts:
    print("=====")
    print(match_text.group())
    print(match_text.group(1))
    print(match_text.group(2))
    print(match_text.group(3))
【実行結果】
<class 'callable_iterator'>
=====
0123-45-6789
0123
45
6789
=====
090-1111-2222
090
1111
2222
=====
9876-54-3210
9876
54
3210
=====
080-3333-4444
080
3333
4444

以下の部分のように findall と使用方法はほとんど同じです。

match_texts = ptrn1.finditer(text)

finditer の返却値は type 関数で確認すると「<class 'callable_iterator'>」となっており、イテレータとなっていることが分かります。そのため以下のように for 文等で順番に取り出すことで処理をすることが可能です。

for match_text in match_texts:
    print('=====')
    print(match_text.group())
    print(match_text.group(1))
    print(match_text.group(2))
    print(match_text.group(3))

for でイテレータから取り出したものは re.Match オブジェクトのため、group メソッドを使うことで、正規表現に一致する文字列全体やサブマッチ文字列を取得して利用することができます。

イテレータの基本については以下も参考にしてください。

イテレータ(iterator)の基本

文字列を分割・置換する

正規表現に一致した部分を基準に文字列を分割するには split メソッドを、正規表現に一致した文字列を置換するには sub メソッドが使用できます。

split メソッドで正規表現で文字列を分割する

正規表現で一致した部分を基準に文字列を分割する場合は、以下のように split メソッドを使用します。

import re

text = (
    "私の電話は0123-45-6789で、携帯は090-1111-2222です。"
    "Aさんの電話は9876-54-3210で、携帯は080-3333-4444です。"
)

# パターンを設定
ptrn1 = re.compile(r"\d{2,4}-\d{2,4}-\d{4}")

# 正規表現で一致した位置を基準に文字列を分割する
split_result = ptrn1.split(text)
print(split_result)
【実行結果】
['私の電話は', 'で、携帯は', 'です。Aさんの電話は', 'で、携帯は', 'です。']

この例は、携帯電話に一致する正規表現を用いて文字列を分割している例です。

まずは、compile 関数にて正規表現を定義します。文字列を分割する際には split メソッドに対象となる文字列全体を渡すだけです。分割された文字列はリスト(list)で取得できます。

split メソッドの公式ドキュメントはこちらを参照してください。なお、正規表現ではなく区切り文字で文字列を分割する場合は、strsplit メソッドを使用するようにしましょう。

sub メソッドで正規表現に一致した文字列を置換する

正規表現に一致した文字列を置換したい場合は、以下のようにsub メソッドを使用します。

import re

text = (
    "私の電話は0123-45-6789で、携帯は090-1111-2222です。"
    "Aさんの電話は9876-54-3210で、携帯は080-3333-4444です。"
)

# パターンを設定
ptrn1 = re.compile(r"\d{2,4}-\d{2,4}-\d{4}")

# 一致する文字列を置換する
replaced_text1 = ptrn1.sub("XXX-XXXX-XXXX", text)
print(replaced_text1)

print("=====")
text = (
    "私の電話は0123-45-6789で、携帯は090-1111-2222です。"
    "Aさんの電話は9876-54-3210で、携帯は080-3333-4444です。"
)

# 置換する数を指定する
replaced_text2 = ptrn1.sub("XXX-XXXX-XXXX", text, 2)
print(replaced_text2)
【実行結果】
私の電話はXXX-XXXX-XXXXで、携帯はXXX-XXXX-XXXXです。Aさんの電話はXXX-XXXX-XXXXで、携帯はXXX-XXXX-XXXXです。
=====
私の電話はXXX-XXXX-XXXXで、携帯はXXX-XXXX-XXXXです。Aさんの電話は9876-54-3210で、携帯は080-3333-4444です。

この例は、電話番号を "XXX-XXX-XXXX" という文字列に置換している例です。

まずは compile 関数にて正規表現を定義します。正規表現に一致した文字列を置換するには、sub メソッドに置換する文字と対象の文字列全体を渡すことで、正規表現に一致した部分を置換することができます。

また、3 つ目の引数の数字(replaced_text2 を作成している上記例では 2) は置き換えの最大個数を指定できます。例では 2 となっているので最初の 2 つは置換がされていますが、それ以降で正規表現に一致する文字列はそのままになります。

sub メソッドの公式ドキュメントはこちらを参照してください。

オプションで正規表現抽出の挙動を制御する

正規表現モジュール re では、各種動作の制御に用いるオプションがあります。以降では、IGNORECASEMULTILINEDOTALLVERBOSE について使い方を紹介します。

大文字/小文字を区別しない(IGNORECASE

正規表現で大文字/小文字を区別せずに抽出したい場合は、re.IGNORECASE をオプションとして指定します。

import re

text = "メールアドレスは、user_01@test.comとプライベート用のUSER_02@test.co.jpを使用しています。"

ptrn = re.compile(
    r"([a-z0-9_.+-]+)@([a-z0-9][a-z0-9-]*[a-z0-9]*\.)+[a-z]{2,}",
    re.IGNORECASE,
)

results = ptrn.finditer(text)
for result in results:
    print(result.group())
【実行結果】
user_01@test.com
USER_02@test.co.jp

この例では、メールアドレスを正規表現で抽出しています。結果として、大文字/小文字に関わらずにメールアドレスの抽出ができていることが分かります。なお、re.IGNORECASE の指定をなくした場合には、2 つ目の「USER_02@test.co.jp」は抽出されなくなります。

上記の例であれば、例えば @ 前までの部分を「[a-zA-Z0-9_.+-]+」というように大文字のパターンも追加することで re.IGNORECASE の指定なしでも抽出することができます。しかし、記述が長くなってしまいますし、記載ミスをしてしまう場合もあります。そのため、オプションをうまく使って対処するのがよいでしょう。

複数行モードを有効にする(MULTILINE

複数行の文字列が \n で改行されているような文字列の場合に、先頭「^」と文字列の末尾「$」の抽出を各行それぞれで行いたい場合は、re.MULTILINE をオプションとして使用します。複数行モードと言ったり、マルチラインモードと言ったりします。

import re

text = "0123-45-6789はAさんの電話番号\n090-1111-2222はBさんの電話番号"

# マルチラインモードではない場合
ptrn1 = re.compile(r"^(\d{2,4})-(\d{2,4})-(\d{4})")

results1 = ptrn1.finditer(text)
for result1 in results1:
    print(result1.group())

print("=====")
# マルチラインモードを使用する場合
ptrn2 = re.compile(r"^(\d{2,4})-(\d{2,4})-(\d{4})", re.MULTILINE)

results2 = ptrn2.finditer(text)
for result2 in results2:
    print(result2.group())
【実行結果】
0123-45-6789
=====
0123-45-6789
090-1111-2222

上記例では、re.MULTILINE を指定しない場合の例も示しています。re.MULTILINE を指定しない場合は、1行目は正規表現に一致して抽出されますが、改行された 2 行目では正規表現を抽出できていません。一方で、re.MULTILINE を指定すると 2 行目についても抽出できていることが分かります。

今回の例では、文字列の先頭である「^」の例となっていますが、文字列の末尾を表す「$」についても同様に挙動の制御を行うことができます。

単一行モードを有効にする(DOTALL

正規表現では「.」は、改行「\n」を除く任意の文字にマッチする表現です。改行も含めてすべての文字にマッチするようにしたい場合は、re.DOTALL をオプションとして指定します。単一行モードと言ったり、シングルラインモードと言ったりします。

import re

text = "単一行モードを使用すると\n改行コードを含めて抽出できる"

# 単一行モードを有効にしない場合
ptrn1 = re.compile(r"^.+")

if result1 := ptrn1.search(text):
    print(result1.group())

print("=====")
# 単一行モードを有効にした場合
ptrn2 = re.compile(r"^.+", re.DOTALL)

if result2 := ptrn2.search(text):
    print(result2.group())
【実行結果】
単一行モードを使用すると
=====
単一行モードを使用すると
改行コードを含めて抽出できる

上記例では、re.DOTALL を指定しない場合の例も示しています。re.DOTALL を指定しない場合は、改行の手前までは抽出されるのですが、改行された 2 行目からは抽出できていません。一方で、re.DOTALL を指定すると改行を含んで 2 行目についても抽出できていることが分かります。

空白/コメントを付与して正規表現を見やすくする(VERBOSE

正規表現の表現は、最初見た人にはなかなか理解が難しいです。私も正規表現については、常に使用するわけではないのでいつも調べながら使用します。

re.VERBOSE オプションを使うと、空白/コメントを正規表現文字列の中に加えることができるので、正規表現に関する説明を付与できます。

import re

text = "私のメールアドレスは、user_01@test.comです。"

ptrn = re.compile(
    r"""
    ([a-z0-9_.+-]+) #local
    @ #delimiter
    ([a-z0-9][a-z0-9-]*[a-z0-9]*\.)+[a-z]{2,} #domain
    """,
    re.VERBOSE,
)

if result := ptrn.search(text):
    print(result.group())
【実行結果】
user_01@test.com

上記の例のように、re.VERBOSE を有効にすると、正規表現内の空白や改行は無視されます。また、「#」でコメントを加えることができるようになります。

正規表現の各部分の説明などをコードに記載しておくと後で内容を確認しやすかったり、他の人に内容を伝えやすくなったりと、可読性を向上させることが可能にります。

複数のオプション値を同時に使用する

上記でいくつかの正規表現のオプションを見てきました。複数のオプションを同時に適用したい場合には、またはを意味する「|」を使用して以下のように連結してあげることで適用することができます。

import re

text = "user_01@test.com: 会社メールアドレス\nUSER_02@test.co.jp: プライベート用メールアドレス"

ptrn = re.compile(
    r"^([a-z0-9_.+-]+)@([a-z0-9][a-z0-9-]*[a-z0-9]*\.)+[a-z]{2,}",
    re.IGNORECASE | re.MULTILINE,
)

results = ptrn.finditer(text)
for result in results:
    print(result.group())
【実行結果】
user_01@test.com
USER_02@test.co.jp

以上のように、オプションもうまく使うと正規表現の扱いが容易になります。

まとめ

Python で正規表現(regular expression)を抽出する際に使用する re モジュールの基本的な使い方について解説しました。

compile して serchmatch で抽出する基本的な方法から、findallfinditer による正規表現に一致する全ての文字列を取得する方法、splitsub を用いた分割・置換の方法、各種オプション(IGNORECASEMULTILINEDOTALLVERBOSE)の使い方について例を使って紹介してきました。

正規表現は、色々な業務システムで利用されます。また、テキストマイニング等の自然言語処理システムでも非常に重要になる技術です。基本的な使い方をしっかりと理解しておきましょう。

re モジュールの公式ドキュメントはこちらを参照してください。

ソースコード

上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。

あわせて読みたい
【Python Tech】プログラミングガイド
【Python Tech】プログラミングガイド
ABOUT ME
ホッシー
ホッシー
システムエンジニア
はじめまして。当サイトをご覧いただきありがとうございます。 私は製造業のメーカーで、DX推進や業務システムの設計・開発・導入を担当しているシステムエンジニアです。これまでに転職も経験しており、以前は大手電機メーカーでシステム開発に携わっていました。

プログラミング言語はこれまでC、C++、JAVA等を扱ってきましたが、最近では特に機械学習等の分析でも注目されているPythonについてとても興味をもって取り組んでいます。これまでの経験をもとに、Pythonに興味を持つ方のお役に立てるような情報を発信していきたいと思います。どうぞよろしくお願いいたします。

※キャラクターデザイン:ゼイルン様
記事URLをコピーしました