公開日:2019/10/20
更新日:2019/10/20
キーワード:Python 正規表現
文字数:5500(読み終わるまでおよそ9分)
この記事でわかること
- メタ文字と特殊シーケンスについて
- Pythonでの正規表現の書き方
はじめに
今回は、Pythonにおける正規表現について見ていきます。正規表現とは、「様々な文字列を一つの文字列で表現する表記法」です。
正規表現を使って、文字列をパターン化した一つの文字列に置き換えることで、文字列の検索や置き換えだけでなく、自然言語処理と呼ばれる高度な処理を行えるようになります。
1. メタ文字と特殊シーケンスについて
メタ文字とは、その文字本来の意味とは異なり、プログラムで特別な意味を持たせた文字のことです。
正規表現のパターンは、「通常の文字」と「メタ文字」が組み合わさって成り立っています。メタ文字には表1のようなものがあります。
表1. メタ文字一覧
メタ文字 | 意味 | 使い方 | マッチする文字列 |
. | 任意の一文字 | a.c | aac, abc, acc |
+ | 直前のパターンの1回以上の繰り返し | ab+ | ab, abb, abbbb |
* | 直前のパターンの0回以上の繰り返し | ab* | a, ab, abb, abbb |
? | 直前のパターンの0回または1回の繰り返し | ab? | a, ab |
{m} | m回の繰り返し | a{3} | aaa |
^ | 文字列の先頭 | ^abc | abcde |
$ | 文字列の末尾 | abc$ | defabc |
{m,n} | m〜n回の繰り返し | a{2, 4} | aa, aaa, aaaa |
[◎-〇] | ◎~〇のうちのどれか1文字。 [ ]で囲んだ文字列を文字クラスと言います。 |
[a-c] | a, b, c |
[◎|〇] | ◎か〇のいずれか | [a|c] | a, c |
[^◎] | ◎以外のいずれかの一文字 | [^a] | b, c, d |
メタ文字以外にも、任意の数字や文字を表すための特殊文字として、特殊シーケンスと呼ばれる表現があります(表2)。特殊シーケンスは、\(バックスラッシュ)を先頭に記載します。
表2. 特殊シーケンス一覧
表現 | 意味 |
\d | 任意の数字 ([0-9]と同じ) |
\D | 任意の数字以外 ([^0-9]と同じ) |
\s | 任意の空白文字 |
\S | 任意の空白文字以外 |
\w | 任意の英数字 |
\W | 任意の英数字以外 |
\A | 文字列の先頭 (^と同じ) |
\Z | 文字列の末尾 ($と同じ) |
メタ文字と特殊シーケンスを使った、正規表現の例を見てみましょう。
①携帯電話の番号(090-1234-5678など)
正規表現を使うと、0[789]0-?[0-9]{4}-?[0-9]{4}、または、0[789]0-?\d{4}-?\d{4}で表せます。
【正規表現の読み方】
・[789]:7、8、9のどれか1文字
・-?[0-9]{4}、または、-?\d{4}:0~9の4文字。「-」があってもなくても検索できるよう、「-」の後に「?」を使用。
②郵便番号(123-4567など)
正規表現を使うと、[0-9]{3}-?[0-9]{4}、または、\d{3}-?\d{4}で表せます。
【正規表現の読み方】
・[0-9]{3}、または、\d{3}:0~9の3文字。
・-?[0-9]{4}、または、-?\d{4}:0~9の4文字。「-」があってもなくても検索できるよう、「-」の後に「?」を使用。
2. Pythonでの正規表現の書き方
Pythonのreモジュールには、正規表現を使った検索、置換、分割などのメソッドがあります。ここでは、reモジュールをインポートして、正規表現を使った文字列検索を行ってみます。
1) 文字列の検索
まずは、正規表現を使わない単純な文字列の検索方法として、findメソッドを見ていきます。
find(文字列, 開始位置, 終了位置) |
findメソッドは、引数で指定した文字列が、検索対象の文字列の中にある場合のインデックス番号を返します(例文3-1)。
<例文3-1>
bunsyo = "I like Python."
position = bunsyo.find("like")
position2 = bunsyo.find("dislike")
print(position)
print(position2)
2
-1
「like」は検索対象の3文字目にあるので、インデックス番号は「2」を返します。そして、指定した文字列が検索対象にない場合は、「-1」を返します。
次に、正規表現を使った文字列の検索方法を見ていきます。reモジュールのsearchメソッドを使うと、マッチオブジェクトのインスタンスを返します。
re.search(正規表現, 検索対象) |
マッチオブジェクトは、検索条件に一致する文字列、開始位置、終了位置などの情報を持っているので、適切なメソッドを使って必要な情報を取り出します(例文3-2)。
なお、re.searchメソッドは、最初に一致した文字列の情報しか取得できないので、注意してください。
<例文3-2>
import re
bunsyo = "000aaa111abc222def"
obj = re.search(r'[a-z]+', bunsyo) #a~zの1回以上の繰り返しの有無を検索
if obj:
print(obj.group()) #検索条件に一致した文字列を返す。
print(obj.start()) #検索条件に一致した文字列の開始位置を返す。
print(obj.end()) #検索条件に一致した文字列の終了位置を返す。(一致した文字列の終了位置+1のインデックス番号が返ってくる)
print(obj.span()) #検索条件に一致した文字列の (開始位置, 終了位置) のタプルを返す。
aaa
3
6
(3, 6)
re.searchメソッドでは、最初に一致した文字列しか見つけてくれませんでしたが、re.findallメソッドを使えば、該当するすべての文字列を抽出できます。
re.findall(正規表現, 検索対象) |
抽出したすべての文字列は、リストで返ってきます(例文3-3)。
<例文3-3>
print(re.findall(r'[a-z]+', bunsyo))
['aaa', 'abc', 'def']
2) 文字列の置換
まずは、正規表現を使わない単純な文字列の置換方法として、replaceメソッドを見ていきます(例文3-4)。
replace(検索文字列, 置換文字列) |
<例文3-4>
bunsyo = "I like Python."
bunsyo2 = bunsyo.replace("I", "You")
print("置換前:", bunsyo , "置換後:", bunsyo2)
置換前: I like Python. 置換後: You like Python.
置換前の文章がはっきり決まっているときは、replaceメソッドを使った置換で対応できます。
次に、正規表現を使った文字列の検索方法を見ていきます。reモジュールのsubメソッドを使うことで、文字列の置換ができます(例文3-5)。
re.sub(正規表現, 置換文字列, 検索対象) |
<例文3-5>
import re
yubin = "140-0014 東京都品川区大井"
yubin2 = re.sub('[0-9]{3}-[0-9]{4}',"***-****",yubin) # 正規表現を使った置換
print (yubin2)
***-**** 東京都品川区大井
「3桁-4桁」の書式で書かれてさえいれば、どんな郵便番号でも置換することがわかります。
正規表現を用いることで、パターンさえ合っていればどのような郵便番号が入っていても、置換することができるため、一般性の高いコードを書くことができます。これが、正規表現を使うことのメリットです。
3) 文字列の分割
文字列を分割したいとき、「,」のように決められた記号や文字で区切られている場合は、通常のsplitメソッドで区切り文字を指定すれば、文字列を分割することができます。
しかし、「abc012def3456ghi7890jklm」のような文字列があり、abcのようにアルファベットのみの文字列を取り出したい場合、区切り文字である数字は、値も違えば桁も違うで、通常のsplitメソッドで分割するのは、かなり大変です。
しかし、正規表現のパターンで文字列を分割できるre.splitメソッドを使えば、簡単に分割できます(例文3-6)。
re.split(正規表現, 分割対象) |
<例文3-6>
bunsyo2 ="abc012def3456ghi7890jklm"
bunkatsu = re.split('\d+', bunsyo2) #1文字以上の任意の数字で分割
print(bunkatsu)
['abc', 'def', 'ghi', 'jklm']
区切り文字は複雑ですが、アルファベットの文字列だけをリストで取り出すことができました。
4) 複数行の操作
複数行の操作をしたい場合は、引数のflagsにre.MULTILINEを指定する必要があります。具体的に見てみましょう。
以下のように、郵便番号と地名が書かれたリストがあるとします。
140-0014 東京都品川区大井 150-0013 東京都渋谷区恵比寿 157-0066 東京都世田谷区成城 |
郵便番号のみを***-****で置換したいとしましょう。
例文3-5で述べたように、正規表現とre.subを組み合わせることで、置換することができます(例文3-7)。
<例文3-7>
import re
# """は複数行の文字列を表します
yubin = """\
140-0014 東京都品川区大井
150-0013 東京都渋谷区恵比寿
157-0066 東京都世田谷区成城
"""
yubin2 = re.sub('[0-9]{3}-[0-9]{4}',"***-****",yubin) # 正規表現を使った置換
print (yubin2)
***-**** 東京都品川区大井
***-**** 東京都渋谷区恵比寿
***-**** 東京都世田谷区成城
数字の3桁-4桁となっている表現をすべて、***-****に変更できました。もちろん、今回の例であればこれでよいのですが、もし住所に番地まで含まれ、その番地が3桁-4桁の表現になっているものがあると、それも***-****に置換されてしまいます(例文3-8)。
<例文3-8>
import re
yubin = """\
140-0014 東京都品川区大井4-5-6
150-0013 東京都渋谷区恵比寿456-7890
157-0066 東京都世田谷区成城123-456
"""
yubin2 = re.sub('[0-9]{3}-[0-9]{4}',"***-****",yubin) # 正規表現を使った置換
print (yubin2)
***-**** 東京都品川区大井4-5-6
***-**** 東京都渋谷区恵比寿***-****
***-**** 東京都世田谷区成城123-456
これは都合が悪いので、文字列の先頭を表すメタ文字「^」(表1参照)を正規表現に追加するのがよいです(例文3-9)。
<例文3-9>
import re
yubin = """\
140-0014 東京都品川区大井
150-0013 東京都渋谷区恵比寿
157-0066 東京都世田谷区成城
"""
yubin2 = re.sub('^[0-9]{3}-[0-9]{4}',"***-****",yubin) # 正規表現を使った置換
print (yubin2)
***-**** 東京都品川区大井
150-0013 東京都渋谷区恵比寿
157-0066 東京都世田谷区成城
これでうまくいくように思えましたが、残念ながら最初の行のみの置換となってしまいました。
そこで、re.subの引数にflags=re.MULTILINEを指定することで解決することができます(例文3-10)。
<例文3-10>
import re
yubin = """\
140-0014 東京都品川区大井
150-0013 東京都渋谷区恵比寿
157-0066 東京都世田谷区成城
"""
yubin2 = re.sub('^[0-9]{3}-[0-9]{4}',"***-****",yubin, flags=re.MULTILINE) # 正規表現を使った置換
print (yubin2)
***-**** 東京都品川区大井
***-**** 東京都渋谷区恵比寿
***-**** 東京都世田谷区成城
これで思惑通り、すべての行の先頭にある郵便番号のみ、***-****に置換することができました。
3. おわりに
今回は、Pythonによる正規表現を使った、文字列の基本的な操作方法を紹介しました。
ある程度パターン化された文字列の操作には、正規表現が威力を発揮します。記号の羅列に見えて取っつきにくい印象はありますが、正規表現のルールを覚えることで、きっと使いこなせるようになるはずです。
データの前処理で正規表現の活躍する場面はあるはずですので、正規表現の使い方を是非マスターしてください。
4. 参考サイト
Python公式ドキュメント re — 正規表現操作
https://docs.python.org/ja/3/library/re.html
書きながら覚えよう!Pythonで正規表現を使う方法
https://techacademy.jp/magazine/15635
pythonで、とっても便利な正規表現を!
https://qiita.com/hiroyuki_mrp/items/29e87bf5fe46de62983c
【Python入門】正規表現の使い方を初心者向けに解説
https://www.sejuku.net/blog/23232#i-2
正規表現:メタ文字(特殊文字)の一覧
http://www-creators.com/archives/2612
Pythonの正規表現で文字列検索・置換・連結・分割する方法
https://shimi-dai.com/python-regular-expression/