今さらPython3 (46) - 正規表現
この本の第7章、正規表現のあたりから。
- 作者: Bill Lubanovic,斎藤康毅,長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/12/01
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
正規表現
全く初めてではない正規表現。これも動かしながら思い出そう。
>>> 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' >>>
これは、グループに名前を付けることが出来るんだね。
(つづく)