0
1

Pythonではじめる正規表現

Posted at

はじめに

正規表現を勉強するときに、どこから手をつけていいのか分からなかったので、実際に使う上で生じた使い方や疑問をまとめました。

基本的な使い方

Pythonの標準モジュールreをインポートします。

import re

以降はインポート文を省略します。
正規表現パターンには2通りの書き方があります。正規表現パターン(pattern)が文字列(string)にマッチするかどうかを調べる書き方は以下のようになります。

# コンパイルする
prog = re.compile(pattern)
result = p.match(string)
# コンパイルしない
result = re.match(pattern, string)

使い分けについては公式リファレンスに説明があります。

re.compile() やモジュールレベルのマッチング関数に渡された最新のパターンはコンパイル済みのものがキャッシュされるので、一度に正規表現を少ししか使わないプログラムでは正規表現をコンパイルする必要はありません。

つまり、1つの正規表現パターンを使い回すときはコンパイルして、使い捨てのパターンのときにコンパイルする必要はありません。

マッチングを実行するメソッド

よく使うであろうものだけを紹介します。その他のものはreドキュメントにあります。

match()

文字列の先頭で正規表現パターンとマッチすれば、Matchオブジェクトを返します。マッチしなければNoneを返します。

# コンパイルする場合
p = re.compile("ab+")
print(p.match("abbc"))  # <re.Match object; span=(0, 3), match='abb'>

# コンパイルしない場合
print(re.match("ab+", "abbc"))  # <re.Match object; span=(0, 3), match='abb'>

# 文字列にマッチしない場合
print(re.match("ab+", "cabbc"))  # None

Matchオブジェクトのメソッドで重要なものは以下のものです。

メソッド 説明
group() マッチした文字列を返す(str)
start() マッチの開始位置を返す(int)
end() マッチの終了位置を返す(int)

ただし、match()メソッドは文字列の先頭でマッチ判定するので、start()の返り値は必ず0になります。

m = re.match("ab+", "abbc")
print(m.group())  # "abb"
print(m.start())  # 0
print(m.end())  # 3

search()

文字列の先頭から走査して、最初にパターンマッチした箇所に対してMatchオブジェクトを返します。マッチしなければNoneを返します。

# コンパイルする場合
p = re.compile("ab+")
print(p.search("cabbc"))  # <re.Match object; span=(1, 4), match='abb'>

# コンパイルしない場合
print(re.search("ab+", "cabbc"))  # <re.Match object; span=(1, 4), match='abb'>

# 文字列にマッチしない場合
print(re.search("ab+", "cbc"))  # None

match()との違いは、match()が文字列の先頭のみ判定するのに対して、search()はパターンが文字列のどこにあっても判定することです。

findall()

パターンマッチした箇所をすべて取り出してリストにします。

# コンパイルする場合
p = re.compile("ab+")
print(p.findall("abc-abbc"))  # ['ab', 'abb']

# コンパイルしない場合
print(re.findall("ab+", "abc-abbc"))  # ['ab', 'abb']

# 文字列にマッチしない場合
print(re.findall("ab+", "acd"))  # []

sub()

パターンマッチした文字列を置換した文字列を返します。
例えば下の例では、パターンab+にマッチする文字列(ab, abb)をxyzに置き換えます。

# コンパイルする場合
p = re.compile("ab+")
print(p.sub("xyz", "abc-abbc"))  # 'xyzc-xyzc'

# コンパイルしない場合
print(re.sub("ab+", "xyz", "abc-abbc"))  # 'xyzc-xyzc'

特殊文字

特殊文字とは、グルーピングや繰り返しなど、特別な意味を持つものを言います。
正規表現で使える特殊文字の一覧は次のとおりです。
. ^ $ * + ? { } [ ] \ | ( )
これらの特殊文字自身をマッチさせるときは、バックスラッシュ\を使います。

# .*は任意の文字列にマッチ
print(re.search("\[.*\]", "example: [xyz]"))  # <re.Match object; span=(9, 14), match='[xyz]'>

改行以外の任意の1文字にマッチ .

print(re.search("a.", "abca"))  # <re.Match object; span=(0, 2), match='ab'>

改行(\n)もマッチさせたい場合は、re.DOTALLを使います。

# コンパイルする場合
p = re.compile("a.", re.DOTALL)
print(p.search("a\nb"))  # <re.Match object; span=(0, 2), match='a\n'>

# コンパイルしない場合
print(re.search("a.", "a\nb", re.DOTALL))  # <re.Match object; span=(0, 2), match='a\n'>

文字列の先頭または末尾にマッチ ^ $

文字列の先頭

print(re.search("^a", "abc"))  # <re.Match object; span=(0, 1), match='a'>
print(re.search("^a", "babc"))  # None

文字列の末尾

print(re.search("a$", "bca"))  # <re.Match object; span=(2, 3), match='a'>
print(re.search("a$", "bcab"))  # None

直前の文字を0回以上繰り返す *

print(re.search("ba*", "baaac"))  # <re.Match object; span=(0, 4), match='baaa'>
print(re.search("ba*", "bc"))  # <re.Match object; span=(0, 1), match='b'>

.と組み合わせると、任意の長さの文字列をマッチさせることができます。

print(re.search("a.*", "xabcabc"))  # <re.Match object; span=(1, 7), match='abcabc'>

直前の文字を1回以上繰り返す +

上記の*と比べると違いが分かりやすいです。

print(re.search("ba+", "baaac"))  # <re.Match object; span=(1, 5), match='baaa'>
print(re.search("ba+", "bc"))  # None

直前の文字を0回または1回繰り返す ?

上記の* +と比べてみてください。

print(re.search("ba?", "baaac"))  # <re.Match object; span=(0, 2), match='ba'>
print(re.search("ba?", "bc"))  # <re.Match object; span=(0, 1), match='b'>

グルーピング ()

繰り返しの* + ?は直前の文字だけでしたが、複数文字を繰り返したい場合があります。
そのときには文字列を()で囲むことにより解決します。

print(re.search("(ba)+", "babaxyz"))  # <re.Match object; span=(0, 4), match='baba'>

どれかの文字にマッチ []

print(re.search("[abc]", "xxbx"))  # <re.Match object; span=(2, 3), match='b'>
print(re.search("[abc]", "xxcx"))  # <re.Match object; span=(2, 3), match='c'>

ハイフン-をつけると、連続した文字の範囲を表すことができます。

# 英小文字にマッチ
print(re.search("[a-z]", "ApB"))  # <re.Match object; span=(1, 2), match='p'>

# 0-9の数字にマッチ
print(re.search("[0-9]", "abc3xyz"))  # <re.Match object; span=(3, 4), match='3'>

バックスラッシュを用いた文字集合

\を使ったものの中には、いくつか特別なパターンを表すものがあります。
代表的なものだけ抜粋します。

文字列 意味 マッチする マッチしない
\d 任意の十進数 0, 6 a, A
\D 任意の非数字文字 a, A 1, 5
\s 任意の空白文字 \n, \t a, 3
\w 任意の英数文字と下線 a, _ \n

Pythonではバックスラッシュ文字はエスケープ文字として機能するため、次のように書く必要があります。

print(re.search("\\d+", "This year is 2000."))  # <re.Match object; span=(13, 17), match='2000'>

バックスラッシュが続くと可読性が下がるので、Raw文字で書く方が分かりやすいです。

print(re.search(r"\d+", "This year is 2000."))  # <re.Match object; span=(13, 17), match='2000'>

マッチした一部分を取り出す方法

ときどき、パターン全体ではなく、一部分だけ欲しいときがあります。
例えば、Yamada Taroという文字列から、苗字だけ取り出したいとします。
このとき、欲しい部分を()でくくると抽出することができます。

m = re.search(r"(\w+) (\w+)", "Yamada Taro")
print(m.group(1))  # 'Yamada'
print(m.group(2))  # 'Taro'

これはキャプチャグループと呼ばれている機能です。

貪欲マッチ・非貪欲マッチ

*+などの繰り返しパターンは、できるだけ長い文字列にマッチしようとします。
そのため、以下の例で前述のキャプチャグループを使って、名前だけ取り出そうとしたときに思ったようにできません。

# []は特殊文字なのでバックスラッシュが必要
m = re.search(r"\[name: (.*)\]", "[name: Suzuki][email: abc@example.com]")
print(m.group(1))  # 'Suzuki][email: abc@example.com'

これを貪欲マッチと呼びます。
上記の例では、Suzukiemailの間にある][で区切りたいです。
このようなとき、繰り返しパターンの後ろに?をつけると、できるだけ短い文字列にマッチしようとするので、取り出すことができます。

m = re.search(r"\[name: (.*?)\]", "[name: Suzuki][email: abc@example.com]")
print(m.group(1))  # 'Suzuki'

短い文字列にマッチさせるのを非貪欲マッチと呼びます。

参考

reリファレンス
正規表現HOW TO
MDN Docs キャプチャグループ

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1