疑念は探究の動機であり、探究の唯一の目的は信念の確定である。

数学・論理学・哲学・語学のことを書きたいと思います。どんなことでも何かコメントいただけるとうれしいです。特に、勉学のことで間違いなどあったらご指摘いただけると幸いです。 よろしくお願いします。くりぃむのラジオを聴くこととパワポケ2と日向坂46が人生の唯一の楽しみです。

Pythonで正規表現について学ぶ 使い方と書き方 その1

概要
正規表現についてPythonで理解する。
まず、Pythonでの正規表現の使い方をまとめて、その後に正規表現について解説する。
最後に、よく使う正規表現のパターンをまとめる。

はじめに 正規表現について

正規表現(regular expression)とは文字列ないで文字の組み合わせを照合するために用いられるパターンのことです。
これだけだと全く意味がわからないと思うので、正規表現が使われるケースを考えて、正規表現の目的を考えてみましょう。

次のような文章があるとします。

A: Pythonって知ってる?
B: うん。しっているよ。pythonでしょ?
C: 俺も知っている。蛇のことだよね?
B: 違うよ。プログラミング言語だよ!
A: 二人ともちがうよ! コメディ番組だよ!!(注: 空飛ぶモンティパイソン)

この文章の中で「Python」や「知っている」や「違う」という言葉が出ていますが、書き方がそれぞれ異なっています。

  • Python: python、パイソン
  • 知っている: 知ってる、しっている
  • 違う: ちがう

ここから例えば「パイソン」という言葉を、その言葉のバラつきを考慮した上で、一気に取得したいとき、正規表現という書き方を用います。

また、同じ日付を表しているデータがあったとしても、あるところでは「2021/08/29」と書かれていて、べつのところでは「21/08/29」と書かれたり、「2021.8.29」と書かれているかもしれません。このようにデータに対してばらつきがあるにもかかわらず、あるパターンで一気に取得したいときに正規表現は使われます。

今回はまずPythonでの正規表現の使い方をまとめて、次に正規表現の書き方を解説します。最後に、よく使うパターンをまとめます。

Section 1 Pythonでの正規表現の使い方

Python正規表現を扱うためにはreモジュールを使います。これは標準ライブラリですので、単にimport reとインポートするだけです。

正規表現のメソッド(正規表現を扱う関数)を使う方法は2種類あります。その違いは筆者にはわかりませんが、ここでは両方を記載します。

Section 1.1 finditer すべてのパターンマッチを取得する

finditerは指定した正規表現にマッチするすべての単語を取得するための方法です。次のコードはその例です。

コード

import re
sentence = 'These are cakes. This is a test! That is a pen! It is on this table!'
pattern = r'[T|t]his|[T|t]hat'
prog = re.compile(pattern)

# find all matches
print('------------- find -------------')
print('------------- prog.finditer(sentence) -------------')
matches = prog.finditer(sentence)
for match in matches:
	print('match = {}'.format(match))
	print('match.span() = {}'.format(match.span()))
	print('match.group() = {}'.format(match.group()))

print('------------- re.finditer(pattern, sentence) -------------')
matches = re.finditer(pattern, sentence)
for match in matches:
	print('match = {}'.format(match))
	print('match.span() = {}'.format(match.span()))
	print('match.group() = {}'.format(match.group()))


結果

------------- find -------------
------------- prog.finditer(sentence) -------------
match = <re.Match object; span=(17, 21), match='This'>
match.span() = (17, 21)
match.group() = This
match = <re.Match object; span=(33, 37), match='That'>
match.span() = (33, 37)
match.group() = That
match = <re.Match object; span=(57, 61), match='this'>
match.span() = (57, 61)
match.group() = this
------------- re.finditer(pattern, sentence) -------------
match = <re.Match object; span=(17, 21), match='This'>
match.span() = (17, 21)
match.group() = This
match = <re.Match object; span=(33, 37), match='That'>
match.span() = (33, 37)
match.group() = That
match = <re.Match object; span=(57, 61), match='this'>
match.span() = (57, 61)
match.group() = this

以下、コードの解説します。

  • 検索される文章はThese are cakes. This is a test! That is a pen! It is on this table!です。
  • 正規表現のパターンはr'[T|t]his|[T|t]hat'です。この意味は「ThisかThatを見つけろ。ただし、先頭のTは小文字tでもいい」です。
  • パターンにr'xxxxxx'がついていますが、これは「正規表現のパターンを使っているよ」ということをPythonに明示するためです。というのも、正規表現のパターンの中には、Pythonエスケープシークエンスと同じ記号があるからです。例えば、\b正規表現としての意味は単語境界(Word Boundary)を意味しますが、エスケープシークエンスとしての意味はASCIIのバックスペース (BS)を意味します。ですので、このr'xxxxxx'は「この文字は正規表現のパターンとして認識してね」ということをPythonに知らせています。基本的には正規表現のパターンにはつけたほうがいいと思います。
  • finditerコンパイルするものとそうでないものがあります。結果は同じです。
  • matchはマッチしているかということを表しています。もし、マッチしていなかったら、match = Noneとなります。
  • match.span()はマッチしている単語がどこにあるかを示しています(0番目基準)。
  • match.group()でマッチしている単語を取得します。
  • 注意: 同じようにパターンマッチする単語を全てリストとして取得する関数にfindallがありますが、あまりお勧めしません(ですので省略します)。

Section 1.2 sub 正規表現にマッチする単語を指定の文字に入れ替える

subは指定した正規表現にマッチする単語を指定の文字に入れ替えるための方法です。次のコードはその例です。
コード

import re
sentence = 'These are cakes. This is a test! That is a pen! It is on this table!'
pattern = r'[T|t]his|[T|t]hat'
prog = re.compile(pattern)

# replace
print('------------- replace -------------')
print('------------- prog.sub(repl, sentence) -------------')
repl = 'TTTT'
repl_sentence = prog.sub(repl, sentence)
print('repl_sentence = {}'.format(repl_sentence))

print('------------- re.sub(pattern, repl, sentence) -------------')
repl_sentence = re.sub(pattern, repl, sentence)
print('repl_sentence = {}'.format(repl_sentence))


結果

------------- replace -------------
------------- prog.sub(repl, sentence) -------------
repl_sentence = These are cakes. TTTT is a test! TTTT is a pen! It is on TTTT table!
------------- re.sub(pattern, repl, sentence) -------------
repl_sentence = These are cakes. TTTT is a test! TTTT is a pen! It is on TTTT table!


subは入れ替えですが、主にパターンマッチする単語をなくすときに使われると思います。つまりこの例でいうと「This、That、this、thatをなくした文章が欲しい」というとき、repl_sentence = re.sub(pattern, '', sentence)と書けばいいです。

Section 1.3 split 正規表現にマッチする単語で文章を分ける

splitは指定した正規表現にマッチする単語で文章を分けてリストを返します。次のコードはその例です。
コード

import re
sentence = 'These are cakes. This is a test! That is a pen! It is on this table!'
pattern = r'[T|t]his|[T|t]hat'
prog = re.compile(pattern)

# split
print('------------- split -------------')
print('------------- prog.split(sentence) -------------')
splited_sentences = prog.split(sentence)
print('splited_sentences = {}'.format(splited_sentences))
for sp_sentence in splited_sentences:
	print('sp_sentence = {}'.format(sp_sentence))

print('------------- re.split(pattern, sentence) -------------')
splited_sentences = re.split(pattern, sentence)
print('splited_sentences = {}'.format(splited_sentences))
for sp_sentence in splited_sentences:
	print('sp_sentence = {}'.format(sp_sentence))


結果

------------- split -------------
------------- prog.split(sentence) -------------
splited_sentences = ['These are cakes. ', ' is a test! ', ' is a pen! It is on ', ' table!']
sp_sentence = These are cakes. 
sp_sentence =  is a test! 
sp_sentence =  is a pen! It is on 
sp_sentence =  table!
------------- re.split(pattern, sentence) -------------
splited_sentences = ['These are cakes. ', ' is a test! ', ' is a pen! It is on ', ' table!']
sp_sentence = These are cakes. 
sp_sentence =  is a test! 
sp_sentence =  is a pen! It is on 
sp_sentence =  table!

Section 1.4 match 文章の先頭が正規表現にマッチするかどうか

matchは指定した文章の先頭が正規表現にマッチするかどうかを調べます。

コード

import re
pattern = r'[T|t]his|[T|t]hat'
prog = re.compile(pattern)

# match
print('------------- match -------------')

#### true case
sentence = 'This is a test! That is a pen! It is on this table!'
match = prog.match(sentence) # It can be written instead re.match(pattern, sentence)
print('match = {}'.format(match))
if match:
	print('match.group() = {}'.format(match.group()))


#### false case
sentence = 'These are cakes. This is a test! That is a pen! It is on this table!'
match = prog.match(sentence) # It can be written instead re.match(pattern, sentence)
print('match = {}'.format(match))
if match:
	print('match.group() = {}'.format(match.group()))

結果

------------- match -------------
match = <re.Match object; span=(0, 4), match='This'>
match.group() = This
match = None
  • 注意: matchは文章の先頭から数えてパターンにマッチするかどうかということしか調べません。ですので、本来はあまり役に立つ機会はありません。パターンにマッチしていなかった場合、Noneを返します。

Section 1.5 search 文章が正規表現にマッチするかどうか

searchは指定した文章が正規表現にマッチするかどうかを調べます。ただし、少なくとも1つマッチしたらそこで処理は終了です。

コード

import re
pattern = r'[T|t]his|[T|t]hat'
prog = re.compile(pattern)

# search
print('------------- search -------------')

#### true case
sentence = 'This is a test! That is a pen! It is on this table!'
match = prog.search(sentence) # It can be written instead re.search(pattern, sentence)
print('match = {}'.format(match))
if match:
	print('match.group() = {}'.format(match.group()))


#### true case
sentence = 'These are cakes. This is a test! That is a pen! It is on this table!'
match = prog.search(sentence) # It can be written instead re.search(pattern, sentence)
print('match = {}'.format(match))
if match:
	print('match.group() = {}'.format(match.group()))

結果

------------- search -------------
match = <re.Match object; span=(0, 4), match='This'>
match.group() = This
match = <re.Match object; span=(17, 21), match='This'>
match.group() = This
  • 注意: searchは先ほどのmatchとは違い、文章にパターンがマッチするかどうかを調べます。ですので、今回は両方ともパターンがマッチしました。パターンにマッチしていなかった場合、Noneを返します。
  • searchmatchはあまり使う機会がないかもしれませんが、「この文章にこれこれのパターンが入っているならば、しかじかの処理をおこなう」などのように「パターンが入っているかどうか」のみを確認するときに使える関数だと思います。

Section 1.6 IGNORECASE 大文字・小文字の区別なしのためのフラグ

これまで「This/this、That/that」をパターンとして認識するために、r'[T|t]his|[T|t]hat'と書きましたが、大文字と小文字の区別をなくして検索するフラグ IGNORECASE を使えば、もう少しすっきりと書くことができます。以下のコードはその例です。

コード

import re
sentence = 'These are cakes. This is a test! That is a pen! It is on this table!'
pattern = r'this|that'
prog = re.compile(pattern, re.IGNORECASE) # instead it can be written  by re.compile(pattern, flags=re.IGNORECASE)

print('------------- Ignore Flag -------------')
print('------------- prog.finditer(sentence) -------------')
matches = prog.finditer(sentence)
for match in matches:
	print('match = {}'.format(match))
	print('match.group() = {}'.format(match.group()))

print('------------- re.finditer(pattern, sentence, re.IGNORECASE) -------------')
matches = re.finditer(pattern, sentence, re.IGNORECASE) # instead it can be written  by re.finditer(pattern, sentence, flags=re.IGNORECASE)
for match in matches:
	print('match = {}'.format(match))
	print('match.group() = {}'.format(match.group()))

結果

------------- Ignore Flag -------------
------------- prog.finditer(sentence) -------------
match = <re.Match object; span=(17, 21), match='This'>
match.group() = This
match = <re.Match object; span=(33, 37), match='That'>
match.group() = That
match = <re.Match object; span=(57, 61), match='this'>
match.group() = this
------------- re.finditer(pattern, sentence, re.IGNORECASE) -------------
match = <re.Match object; span=(17, 21), match='This'>
match.group() = This
match = <re.Match object; span=(33, 37), match='That'>
match.group() = That
match = <re.Match object; span=(57, 61), match='this'>
match.group() = this

まとめ

正規表現の基本操作はだいたいこんな感じです。
次は正規表現のパターン自体を解説します。