[Python] 正規表現の表記方法のまとめ(reモジュール)

Pythonで正規表現を使うには、標準ライブラリのreモジュールを使います。
とは言っても正規表現はとても複雑で、慣れていない方にとっては(自分も含めて)覚えるのもなかなか大変です。
そこで、本記事ではPythonで正規表現を使う際に用いる正規表現の表記方法それぞれについて、具体的な例も交えながらまとめてみました。

#2019/3/24 記事更新

確認した環境

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

正規表現を用いたパターンマッチング

正規表現を用いたパターンマッチングを行うには、reモジュールを使います。
処理の基本的な流れは以下です。

>>> import re

# 正規表現パターンをコンパイルし、正規表現オブジェクトを生成
>>> regex = re.compile(r'\d{4}')

# Matchオブジェクトを生成
>>> mo = regex.search('abc123def')

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

<関連記事>

尚、上記では正規表現オブジェクトとMatchオブジェクトを別々のメソッドを使って生成しました。
reモジュールはこれらの機能を一つにまとめたメソッドも提供しています。
今回の記事は、そちらを使って解説します。例えば、

regex = re.compile(pattern, flags=0)
mo = regex.search(string)

という処理は、

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

を使うと、一つの関数でMatchオブジェクト生成まで出来ます。
尚、上記の関数は、マッチした結果を出力します。

正規表現の表記の仕方

正規表現の表記法の概要を下表にまとめました。
具体的な使用例は次章で記載します。

記号内容
\d 0~9の数字
\D0~9の数字以外
\w文字、数字、下線
\W文字、数字、下線以外
\s空白文字(スペース、タブ、改行)
\S空白文字(スペース、タブ、改行)以外
{n}直前の文字列またはグループがn回出現
{n,}直前の文字列またはグループがn回以上出現
{n,m} 直前の文字列またはグループがn〜m回出現
{,m} 直前の文字列またはグループが0〜m回出現
[abc]マッチさせたい文字を並べ、独自の文字集合を作成。
大文字小文字も区別されます。
[a-z]ハイフン'-'を用いると、文字や数値の範囲が指定される
[^a-z][の直後に'^'をつけ、文字の「補集合」を表す
^(キャレット)文字列の先頭にマッチする
$文字列の末尾にマッチする
.(ピリオド)改行以外の全ての文字を表す。
但し、DOTALLフラグが設定された場合は、改行も含まれる。
|(縦線)複数のパターンのうち一つとマッチ。
候補が両方存在する場合は最初にマッチしたほうが出力される。
?直前の文字列またはグループに0回または1回マッチ
*直前の文字列またはグループに0回以上、できるだけ多くマッチ
+直前の文字列またはグループに1回以上、できるだけ多くマッチ
{}?、 *?、 +?、 ??非貪欲(non-greedy)マッチ(後述)

具体的な使用例

正規表現を表記するには、’r’ を正規表現パターンの先頭に置きます。(raw string 記法)
※ここの詳細は、公式リファレンス re — 正規表現操作を参照

文字列の短縮形

# 検索対象の文字列。スペースやカンマなども含めています
>>> s = '123 abc._,'

# 0〜9の数字にマッチした1文字を抽出
>>> re.findall(r'\d', '123 abc._')
['1', '2', '3']
	
# 0〜9の数字以外にマッチした1文字を抽出
>>> re.findall(r'\D', '123 abc._')
[' ', 'a', 'b', 'c', '.', '_']
	
# 文字、数字、下線にマッチした1文字を抽出
>>> re.findall('r\w', '123 abc._,')
['1', '2', '3', 'a', 'b', 'c', '_']
	
# 文字、数字、下線以外にマッチした1文字を抽出
>>> re.findall('r\W', '123 abc._,')
[' ', '.', ',']

# スペース、タブ、改行にマッチした1文字を抽出
>>> re.findall('r\s', '123 abc._,')
[' ']

# スペース、タブ、改行以外にマッチした1文字を抽出
>>> re.findall('r\S', '123 abc._,')
['1', '2', '3', 'a', 'b', 'c', '.', '_', ',']

{}:複数回指定

連続して出現する回数を{ }で囲みます。

# 連続3回出現した場合にマッチング
>>> re.search(r'\d{3}', '0123456789').group()
'012'

# 連続3回以上出現した場合にマッチング → 10個連続の数字がマッチ
>>> re.search(r'\d{3,}', '0123456789').group()
'0123456789'

# 連続3回から5回出現した場合にマッチング → 5個連続の数字がマッチ
>>> re.search(r'\d{3,5}', '0123456789').group()
'01234'

# 連続5回以下出現した場合にマッチング → 5個連続の数字がマッチ
>>> re.search(r'\d{,5}', '0123456789').group()
'01234'

[abc]:文字集合を独自で定義

マッチさせたい文字を並べ、[ ]で囲みます。

# マッチさせたい文字を並べ、独自の文字集合を作成
>>> re.findall(r'[acf]', 'abcdef')
['a', 'c', 'f']

# 大文字小文字も区別されます
>>> re.findall(r'[aAbB]', 'AaBbcdef')
['A', 'a', 'B', 'b']

[a-z](ハイフン):文字や数値の範囲を指定

アルファベットや数値の範囲を、-(ハイフン)でつなげ、[ ]で囲みます。

# a〜zの英小文字が5回連続した場合にマッチ
>>> re.findall(r'[a-z]{5}', '123ab-12345-abc12-abcdef')
['abcde']

# 0〜9の数字が5回連続した場合にマッチ
>>> re.findall(r'[0-9]{5}', '123ab-12345-abc12-abcdef')
['12345']

# a〜zの英小文字または0〜9の数字が5回連続した場合にマッチ
>>> re.findall(r'[a-z0-9]{5}', '123ab-12345-abc12-abcdef')
['123ab', '12345', 'abc12', 'abcde']

[^(文字集合)](キャレット):(文字集合)以外を抽出

^(キャレット)を[の直後(=文字集合の直前)に挿入します。

# a, b, c以外の文字を抽出
>>> re.findall(r'[^abc]', 'abcdef')
['d', 'e', 'f']

# a〜zの英小文字および'-'(ハイフン)以外の文字が5回連続した場合にマッチ
>>> re.findall(r'[^a-z-]{5}', '123ab-12345-abc12-abcdef')
['12345']
	
# 0〜9の数字および'-'(ハイフン)以外の文字が5回連続した場合にマッチ
>>> re.findall(r'[^0-9-]{5}', '123ab-12345-abc12-abcdef')
['abcde']

# a〜zの英小文字、0〜9の数字、'-'(ハイフン)以外の文字が5回連続した場合にマッチ
>>> re.findall(r'[^0-9a-z-]{5}', '123ab-12345-abc12-abcdef')
[]

^(キャレット), $(ドル):検索対象の先頭、末尾にマッチ

文字列の先頭にマッチさせる場合は^(キャレット),末尾にマッチさせる場合は $(ドル)を挿入します。

# キャレット'^'は、文字列の先頭にマッチ
>>> re.findall(r'^[0-9]{3}', '123abc')
['123']

# 文字列の先頭にマッチするパターンが無い場合
>>> re.findall(r'^[0-9]{3}', 'abc123')
[]

# ドル'$'は文字列の末尾にマッチ
>>> re.findall(r'[0-9]{3}$', 'abc123')
['123']

# 文字列の末尾にマッチするパターンがない場合
>>> re.findall(r'[0-9]{3}$', '123abc')
[]

.(ピリオド):ワイルドカード

# ^と$と組み合わせることによって、文字列全体がマッチします
>>> re.search(r'^(.*)$', 'I live in Tokyo.').group() 
'I live in Tokyo.'

# ドット'.'は、改行以外の全ての文字とマッチします
>>> re.search(r'.*', 'I live in Tokyo.\nShe lives in Yokohama.').group() 
'I live in Tokyo.'

# DOTALLフラグが設定された場合は、改行も含まれます。
>>> re.search(r'.*', 'I live in Tokyo.\nShe lives in Yokohama.', re.DOTALL).group() 
'I live in Tokyo.\nShe lives in Yokohama.'

|(縦線):複数のグループとのマッチング

# 検索文字列の中にマッチするパターンが両方ある場合は、最初にマッチしたほうが出力されます
>>> re.search(r'Tokyo|Paris', 'Tokyo, London, Paris').group()
'Tokyo'

>>> re.search(r'Tokyo|Paris', 'Paris, London, Tokyo').group()
'Paris'

?:直前のグループ(文字列)に0回か1回マッチ

>>> re.search(r'(Orange)?jurce', 'Orangejurce, Cake').group()
'Orangejurce'

>>> re.search(r'(Orange)?jurce', 'jurce, Cake').group()
'jurce'

*:直前のグループ(文字列)に0回以上、できるだけ多くマッチ

# 直前の文字列(この場合はx)と1回マッチング
>>> re.search(r'x*y', 'xy').group()
'xy'

# できるだけ多くマッチング
>>> re.search(r'x*y', 'xxxy').group()
'xxxy'

# 直前の文字列とマッチなし(0回)
>>> re.search(r'x*y', 'y').group()
'y'

+:直前のグループ(文字列)に1回以上、できるだけ多くマッチ

# 直前の文字列と1回マッチング
>>> re.search(r'x+y', 'xy').group()
'xy'

# できるだけ多くマッチング
>>> re.search(r'x+y', 'xxxxy').group()
'xxxxy'

# 直前の文字列とマッチなし
>>> re.search(r'x+y', 'y') == None
True

貪欲マッチと非貪欲マッチ

貪欲(greedy)マッチ

‘*’、’+’、’?’、および'{n,m}’はできるだけ多くの文字列とマッチした結果を出力しようとします。つまり、マッチング回数が最大の候補が出力されます。これを貪欲(greedy)マッチと呼びます。

例えば、文字列’0123456’に対し、正規表現パターンをr’\d{3,5}’(=繰り返し3回〜5回)でパターンマッチングを行う場合、マッチング候補は’012’、’0123’、’01234’の3通りあります。Pythonの正規表現は貪欲なので、「連続5回出現」の条件にマッチした文字列’01234’が出力されます。

以下に例を示します。

# {n, m}の例 → 6個連続の数字がマッチ
>>> re.search(r'\d{3,}', '012345').group()
'012345'

# '*'の例(改行以外の文字列が0回以上マッチ)  
>>> re.search(r'<.*>', '<h2>How to make a Cake</h2>') .group()
'<h2>How to make a Cake</h2>'

# '+'の例(改行以外の文字列が1回以上マッチ)  
>>> re.search(r'<.+>', '<h2>How to make a Cake</h2>') .group()
'<h2>How to make a Cake</h2>'

# '?'の例(文字列'abc'が0回か1回マッチ)
>>> re.search(r'(abc)?', 'abc123').group()
'abc'

非貪欲(non-greedy)マッチ

逆にできるだけ少なくマッチングした結果を出力することも可能です。これを非貪欲(non-greedy)マッチと呼びます。非貪欲マッチを設定するには、繰り返し記号(*, , +, ?, {})の後ろに’?’を付けます。

以下に例を示します。上記の例にそれぞれ’?’を追加して非貪欲化してみました。

# {n, m}の例 → 3個連続の数字がマッチ
>>> re.search(r'\d{3,}?', '012345').group()
'012'

# '*'の例(パターンマッチングした文字列が一番少ない) 
>>> re.search(r'<.*?>', '<h2>How to make a Cake</h2>') .group()
'<h2>'

# '+'の例(パターンマッチングした文字列が一番少ない)
>>> re.search(r'<.+?>', '<h2>How to make a Cake</h2>') .group()
'<h2>'

# '?'の例(文字列'abc'が0回マッチ)
>>> re.search(r'(abc)??', 'abc123').group()

まとめ

今回は、Pythonでの正規表現パターンの表し方についてまとめました。前回のパターンマッチングの方法と合わせて、Webスクレイピングや言語解析の前処理(文字列整形)等で使われる技術だと思いますので、理解して進めたいと思います。

Learn more...

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

[Sponsor link]