【Python】正規表現 reモジュールの使いかたの基本

Pythonで正規表現マッチング操作を行うには、標準組込みのreモジュールを使います。本記事では、

  • reモジュールを使った正規表現マッチング操作の基本、および
  • 文字列の置き換えや分割等の文字列操作

について具体例を挙げながら整理します。
尚、正規表現パターンの作り方(書式)については別の記事でまとめていますのでそちらもご参照ください。
# 2019/8/18 記事更新

確認した環境

  • OS: Ubuntu 16.04LTS
  • Python: ver3.7.2

正規表現とは?

ビギナーの方はこの言葉に馴染みがない方もいると思いますので、備忘録も兼ねてWikipediaから抜粋します。

正規表現(せいきひょうげん、英: regular expression)とは、文字列の集合を一つの文字列で表現する方法の一つである。(中略)アプリケーションやプログラミングにおいて正規表現を用いた文字列のパターンマッチングを行う機能のことを、単に正規表現という。

Wikipedia「正規表現」

reモジュールを使った正規表現マッチング操作

reモジュールを使った正規表現マッチング操作(=正規表現にマッチした文字列を取得する操作)の基本的な流れは以下です。

  1. reモジュールをインポートする
  2. 正規表現パターンを使って、正規表現オブジェクトを作る
  3. マッチオブジェクトを作る
  4. マッチング結果を出力

これをコード風に書くと下記のようになります。

# reモジュールのインポート
import re

# 正規表現オブジェクトを作る
regex = re.compile(正規表現パターン)

# マッチオブジェクトを作る
mo = regex.search(検索対象)

# マッチング結果を出力
mo.group()

正規表現オブジェクトを作る

compile()メソッドを使います。
使い方は下記です。

re.compile(pattern, flags=0)

検索したい文字列の正規表現パターンpatternを引数に設定します。返り値は正規表現オブジェクトです。下記にいくつか例を示します。

# 特定の文字列(ここでは 'abc')を検索する場合
>>> regex = re.compile(r'abc')
>>> type(regex)
<class 're.Pattern'> # 正規表現オブジェクト

# 正規表現パターン(0〜9の数字を検索する場合は、'\d')を指定
>>> regex = re.compile(r'\d')
>>> type(regex)
<class 're.Pattern'> # 正規表現オブジェクト

正規表現パターンの作り方についてはこちらの記事でまとめていますので、ご参照ください。
[Python] 正規表現の表記方法のまとめ(reモジュール)

マッチオブジェクトを作る

上記で生成した正規表現オブジェクトに対し、search()を使います。
これは、正規表現パターンに最初にマッチした文字列を探して、マッチオブジェクトを返します。
使い方は以下です。

Pattern.search(string[, pos[, endpos]])

検索対象の文字列を第一引数stringに設定します。
尚、第二引数以降はそれぞれ、

  • 第二引数pos: 文字列のどこから探し始めるかを指定するインデックス(デフォルトは 0)
  • オプション引数endpos: 文字列がどこまで検索されるかを指定

を表しますが、詳細は公式リファレンスを参照してください。

具体的な例を示します。

>>> import re

# 特定の文字列(ここでは 'abc')を検索する場合
>>> regex = re.compile(r'abc')

# 最初にマッチした文字列を探してマッチオブジェクトを返す
>>> mo = regex.search('abc123abc456')
>>> type(mo)
<class 're.Match'> # マッチオブジェクト

>>> mo
<re.Match object; span=(0, 3), match='abc'>

# 引数pos, endposを設定:(3, 10)の範囲で検索
>>> mo = regex.search('abc123abc456', 3, 10)
>>> mo
<re.Match object; span=(6, 9), match='abc'>

# マッチしたパターンが見つからない場合はNoneを返します
>>> mo = regex.search('123456')
>>> mo
>>> print(mo)
None

生成されたマッチオブジェクトを確認すると、文字列のどこでどの文字がマッチングしているのかがわかります。今回の例では、検索対象の文字列の[0:3]や[6:9]の箇所で、文字列’abc’でマッチングしています。

マッチング結果を出力する

マッチした文字列を返す

上記で生成したマッチオブジェクトに対し、group()を使います。
これは、グルーピング(※)されたマッチング結果の文字列を返します。使い方は以下です。
※グルーピングについては後述

match.group([group1, ...])

引数group1,,,は「サブグループ名」で、省略した場合は0(=デフォルト値)となり、マッチング結果のサブグループを全て出力します。
具体例を以下に示します。

>>> import re

# グルーピングを使って正規表現パターンを生成
>>> regex = re.compile(r'(\d{3})-(\d{3})-(\d{3})')

# マッチオブジェクトを生成
>>> mo = regex.search('123-456-789')

# グルーピング名を省略 or 0を指定すると全部出力される
>>> mo.group()
'123-456-789'

>>> mo.group(0)
'123-456-789'

# グルーピング名をそれぞれ指定した場合
>>> mo.group(1)
'123'
>>> mo.group(2)
'456'
>>> mo.group(3)
'789'

マッチング箇所をインデックスで取得

生成したマッチオブジェクトに対しspan()を使うと、マッチング箇所を2タプル(start, end)で返します。使い方は以下です。

Match.span([group])

引数groupは上記同様にサブグループ名を示し、ここに設定したグループ名のインデックスが返ります。省略または0を指定すると全部出力されます。
具体的な例を示します。

>>> import re

# グルーピングを使って正規表現パターンを生成
>>> regex = re.compile(r'(\d{3})-(\d{3})-(\d{3})')

# マッチオブジェクトを生成
>>> mo = regex.search('123-456-789')

# 引数を省略するか0の場合はマッチング結果全体を返します
>>> mo.span()
(0, 11)
>>> mo.span(0)
(0, 11)

# グルーピング名をそれぞれ指定した場合
>>> mo.span(1)
(0, 3)
>>> mo.span(2)
(4, 7)
>>> mo.span(3)
(8, 11)

<参考>これは、str.find()とほぼ同様の機能で、文字列の中で特定のキーワードを検索するのにも使えます。こちらの記事もご参照ください。
[Python] 文字列を検索する(in演算子, find)

グルーピングについて

正規表現パターンの一部をカッコ”()”を使ってグルーピングすることができます。
各グループには名前が付けられており、デフォルトは1〜99の整数値です。
また、シンボリックグループ名(?P…)を使うと、文字列で表すこともできます。

# 3桁の数字がハイフン'-'で4つ繋がったパターンを検出する場合
>>> regex = re.compile(r'(\d{3})-(\d{3})-(\d{3})-(\d{3})')

# それぞれのグループ名を" ?P<...> "で設定
>>> regex = re.compile(r'(?P<area>\d{3})-(?P<local>\d{3})-(?P<id>\d{4})')
  • グループ名をデフォルト(整数値)で指定する場合
  • # 市外局番を含む電話番号(3桁数字-3桁数字-4桁数字)の例
    >>> regex = re.compile(r'(\d{3})-(\d{3})-(\d{4})')
    >>> mo= regex.search('Phone number is 012-345-6789.')
    
    # group()の引数(=グループ名)を省略すると全部出力	
    >>> mo.group()
    '012-345-6789'
        
    # グループ名=0の場合も全部出力
    >>> mo.group(0)
    '012-345-6789'
        
    # グループ名=1のみを出力	
    >>> mo.group(1)
    '012'
        
    # グループ名を複数指定すると、タプルで出力	
    >>> mo.group(2,3)
    ('345', '6789')
  • グループ名を文字列で指定する場合
  • # それぞれのグループ名を、?P<...>で設定
    >>> regex = re.compile(r'(?P<area>\d{3})-(?P<local>\d{3})-(?P<id>\d{4})')
    >>> mo= regex.search('Phone number is 012-345-6789.')
    
    # グループ名を文字列で指定
    >>> mo.group('area')
    '012'
    >>> mo.group('local', 'id')
    ('345', '6789')
        
    # 整数値でも使えます
    >>> mo.group(0)
    '012-345-6789'
    
    >>> mo.group(1)
    '012'

マッチング結果を全て出力

上述したsearch()最初のマッチング結果のみに対応するマッチオブジェクトを返しましたが、マッチング結果を全て出力したい場合は、正規表現オブジェクトに対し、findall()を使います。

Pattern.findall(string[, pos[, endpos]])

引数はsearch()と同様ですが、返り値はマッチング結果をリストで出力します。
例を以下に示します。

>>> import re

>>> regex = re.compile(r'\d{3}')

# 結果はリストで出力されます
>>> regex.findall('123-456-789-abc')
['123', '456', '789']

また、次の様に正規表現オブジェクト生成(re.compile)を省略して、簡易的にre.findall()を使うこともできます。

re.findall(pattern, string, flags=0)

第一引数に正規表現パターンを設定する以外、第二引数以降は上記と同じです。
使用例を以下に記載します

>>> re.findall(r'\d{3}', '123-456-789-abc')
['123', '456', '789']

こちらの方が簡単に書けますが、同じ正規表現を何回も使う場合は、前記のように正規表現オブジェクトを保存して再利用するほうが効率的とのことです。(公式リファレンスより)

文字列の置き換え

正規表現オブジェクトに対してsub()を使うと、文字列の置き換えができます。
使い方は以下です。

regex.sub(repl, string, count=0)

返り値は、置き換え後の文字列です。

  • 第一引数repl: 置き換える文字列
  • 第二引数string: 元の文字列
  • オプション引数count: 置き換えられるパターンの出現回数の最大値
    ※このパラメータが省略 or 0の場合は、マッチした文字列全てが置換されます。

具体的な例を以下に示します。

>>> regex = re.compile(r'\d{3}')

# countパラメータが省略 or 0の場合は全て置換
>>> regex.sub('***', '123-456-789-abc')
'***-***-***-abc'

# countパラメータを1以上の数値に設定した場合
>>> regex.sub('***', '123-456-789-abc', 1)
'***-456-789-abc'
>>> regex.sub('***', '123-456-789-abc', 2)
'***-***-789-abc'
>>> regex.sub('***', '123-456-789-abc', 3)
'***-***-***-abc'

また、次の様に正規表現オブジェクト生成(re.compile)を省略して、簡易的にre.sub()を使うこともできます。

re.sub(pattern, repl, string, count=0, flags=0)

第一引数に正規表現パターンを設定する以外、第二引数以降は上記と同じです。
使用例を以下に記載します

>>> re.sub(r'\d{3}', '***', '123-456-789-abc')
'***-***-***-abc'
>>> re.sub(r'\d{3}', '***', '123-456-789-abc', 1)
'***-456-789-abc'
>>> re.sub(r'\d{3}', '***', '123-456-789-abc', 2)
'***-***-789-abc'
>>> re.sub(r'\d{3}', '***', '123-456-789-abc', 3)
'***-***-***-abc'

<参考>マッチングする文字列を特定すると、str.replace()メソッドと同様な機能が得られます。こちらの記事もご参照ください。
[Python] 文字列から文字(文字列)や空白文字を削除する (replace, strip, rstrip, lstrip)

文字列の分割

正規表現オブジェクトに対しsplit()を使うと、文字列を指定した文字列で分割できます。分割された文字列がリストで返ります。
使い方は以下です。

regex.split(string, maxsplit=0)

引数はそれぞれ以下です。

  • 第一引数string: 分割する文字列
  • これをパターンマッチした文字列があるたびに分割します。

  • 第二引数maxsplit:分割数
  • 例えばmaxsplit=1の場合は、1個の分割が発生(要素は2つ)し、残りの文字列は最終要素として返されます。尚、デフォルト値は0で、この場合は全て分割されます。

例を以下に示します。

>>> regex = re.compile(r'@')

# '@'で文字列を分割
>>> regex.split('abc.def@example@xyz.com')
['abc.def', 'example', 'xyz.com']

# maxsplit=1の場合は1回の分割 
>>> regex.split('abc.def@example@xyz.com', 1)
['abc.def', 'example@xyz.com']

また、次の様に正規表現オブジェクト生成(re.compile)を省略して、簡易的にre.split()を使うこともできます。

re.split(pattern, string, maxsplit=0, flags=0)

第一引数に正規表現パターンを設定する以外、第二引数以降は上記と同じです。
使用例を以下に記載します。

>>> re.split(r'@', 'abc.def@example@xyz.com')
['abc.def', 'example', 'xyz.com']

<参考>マッチングする文字列を特定するとstr.split()メソッドと同様な機能が得られます。以下の記事もご参照ください。
[Python] 文字列を任意の区切り文字で分割する(splitの使い方)

まとめ

正規表現の使い方の基本について、

  • reモジュールを使った正規表現オブジェクトの生成(compile()メソッド)
  • Matchオブジェクト生成(search()オブジェクト)による抽出結果の出力方法
  • 文字列の置き換え(sub()メソッド)
  • 文字列の分割(split()メソッド)

についてまとめました。

Learn more...

書籍でもう少し詳しく学びたい場合はこちらもどうぞ。筆者もかなり参考にさせてもらっています!

[Sponsor link]