今さらPython3 (46) - 正規表現

この本の第7章、正規表現のあたりから。

入門 Python 3

入門 Python 3

正規表現

全く初めてではない正規表現。これも動かしながら思い出そう。

>>> import re
>>> source = 'Young Frankenstein'
>>> m = re.match('You', source)
>>> m
<_sre.SRE_Match object; span=(0, 3), match='You'>
>>> if m:
...     print(m.group())
... 
You

出だしがYouで始まっているかの確認をしていて、今回のケースはマッチしているからmにTrueが返っているのかな。

>>> m = re.match('^You', source)
>>> if m:
...     print(m.group())
... 
You
>>> m = re.match('Frank', source)
>>> if m:
...     print(m.group())
... 
>>> 

match()は、パターンの先頭にある場合の判定に使うのね。^も先頭を意味する記号だったと記憶。

>>> m = re.search('Frank', source)
>>> if m:
...     print(m.group())
... 
Frank

search()の場合は、sourceの中のどこにあってもOK。

>>> m = re.match('.*Frank', source)
>>> if m:
...     print(m.group())
... 
Young Frank
>>> 

.が任意の1文字、*が繰り返しだから、先頭からFrankと一致する箇所までがマッチした文字列と判定されると。

>>> m = re.search('Frank', source)
>>> if m:
...     print(m.group())
... 
Frank
>>> 

search()を使えば、Frankだけが結果として返ってくる。

>>> m = re.findall('n', source)
>>> m
['n', 'n', 'n', 'n']
>>> print('Found', len(m), 'matches')
Found 4 matches
>>> 

re.findall()の挙動は上みたいな感じ。今回は、mに直接リストが返ってきている。

>>> m = re.findall('n.?', source)
>>> m
['ng', 'nk', 'ns', 'n']
>>> m = re.findall('n.', source)
>>> m
['ng', 'nk', 'ns']
>>> 

これは、nの後ろに任意の文字が付いているものを見つけようとしている。?のあるなしで、nが選ばれているか否かが違う。.が任意であることを示しているってことだね。

>>> m = re.split('n', source)
>>> m
['You', 'g Fra', 'ke', 'stei', '']
>>> n = source.split('n')
>>> n
['You', 'g Fra', 'ke', 'stei', '']
>>> 

正規表現ではない、普通のsplit()とも比べてみた。

>>> m = re.sub('n', '?', source)
>>> m
'You?g Fra?ke?stei?'
>>> 
>>> m = re.sub('\?', 'n', source)
>>> m
'Young Frankenstein'
>>> m = re.sub('n.?', '?', source)
>>> m
'You? Fra?e?tei?'
>>> 

sub()はsubstitutionから来ているのかな。クエスチョンマークそのもを置き換えたいなら、\?と書けば良い。

一通りやってみて

こんな特殊文字があるよという表が載っているけど、こういうのは本家のドキュメントに当たった方が良いと思うので、ひとまずリンクをここに。

6.2. re — 正規表現 — Python 3.4.3 ドキュメント

さらに実験。

>>> import string
>>> printable = string.printable
>>> len(printable)
100
>>> printable[0:50]
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN'
>>> printable[50:]
'OPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

サンプルデータとして使うわけですね。

>>> re.findall('\d', printable)
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
>>> re.findall('\D', printable)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~', ' ', '\t', '\n', '\r', '\x0b', '\x0c']
>>> 

\dが数字、\Dが数字以外が選ばれているのが分かる。

>>> re.findall('\s', printable)
[' ', '\t', '\n', '\r', '\x0b', '\x0c']
>>> re.findall('\S', printable)
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~']
>>> 

\sは空白文字とあるけど、単にスペースという意味ではないんだね。\Sは\s以外という事で文字として認識されるものが出てくる。

>>> x = 'abc' + '-/*' + '\u00ea' + '\u0115'
>>> x
'abc-/*êĕ'
>>> re.findall('\w', x)
['a', 'b', 'c', 'ê', 'ĕ']
>>> re.findall('\W', x)
['-', '/', '*']

これは\w(\W)の動作を見ている。英字(英字以外)のものが選択されてきていて、アクセント記号付きの字も英字として認識されていますと。

メタ文字

これもリストが本に載っているけど、6.2. re — 正規表現 — Python 3.4.3 ドキュメントに載っているので、そちらを参照。

>>> source = '''I wish I may, I wish I might
... Have a dish of fish tonight.'''
>>> 
>>> re.findall('wish', source)
['wish', 'wish']
>>> re.findall('wish|fish', source)
['wish', 'wish', 'fish']
>>> re.findall('^wish', source)
[]
>>> re.findall('^I wish', source)
['I wish']
>>> re.findall('fish$', source)
[]
>>> re.findall('fish tonight.$', source)
['fish tonight.']
>>> re.findall('fish tonight\.$', source)
['fish tonight.']
がor条件になっているのと、^が先頭、$が末尾を表している。tonight.$はtonightの後ろが任意の1文字という解釈をされるので、ピリオドがあるという意味では、tonight\.$と表記するのが正しい。なるほどね。
>>> re.findall('[wf]ish', source)
['wish', 'wish', 'fish']
>>> 

wish|fishよりも簡単だね。

>>> re.findall('[wsh]', source)
['w', 's', 'h', 'w', 's', 'h', 'h', 's', 'h', 's', 'h', 'h']
>>> re.findall('[wsh]+', source)
['w', 'sh', 'w', 'sh', 'h', 'sh', 'sh', 'h']
>>> 

w,s,hのいずれかを選ぶパターンとw,s,hが1文字以上続くパターンを+のありなしで分けている。

>>> re.findall('ght\W', source)
['ght\n', 'ght.']

ghtに続いて文字以外のものが来ているパターンだね。

>>> re.findall('I (?=wish)', source)
['I ', 'I ']

I wishのうち、’I '(Iとスペース)を抽出している。

>>> re.findall('\bfish', source)
[]
>>> re.findall(r'\bfish', source)
['fish']
>>> 

pythonのエスケープ文字との混乱を避けるために、これは正規表現のためのパターンですよという時は、明示的にrを先頭に付けてやればよい。

>>> m = re.search(r'(.dish\b).*(\bfish)', source)
>>> m.group()
' dish of fish'
>>> m = re.search(r'(. dish\b).*(\bfish)', source)
>>> m.group()
'a dish of fish'
>>> m.groups()
('a dish', 'fish')
>>> 

match()やsearch()を使った場合は、groups()やgroup()に格納されるという説明。「それらのタプル」という説明がよく分からなかったので、こんな実験をしてみた。

>>> m = re.search(r'. dish\b.*\bfish', source)
>>> m.groups()
()
>>> m.group()
'a dish of fish'
>>> 

groups()だと、()でくくった単位ごとにタプルができるんだね。

>>> m = re.search(r'(?P<DISH>. dish\b).*(?P<FISH>\bfish)', source)
>>> m.group()
'a dish of fish'
>>> m.groups()
('a dish', 'fish')
>>> m.group('DISH')
'a dish'
>>> m.group('FISH')
'fish'
>>> 

これは、グループに名前を付けることが出来るんだね。

(つづく)