正規表現のre.search().groups()とre.findall()の違い【Python】

Oct. 11, 2019, 12:12 a.m. edited Aug. 7, 2020, 8:26 a.m.

#正規表現  #Python 

大学のプログラミングの授業(Python)でTAをしているのですが、ここで

abc123xyz

に含まれる数字を正規表現で抜き出す(出力例は123)という課題が出ました1。この解法として、

>>> import re
>>> re.search('\d+', 'abc123xyz').group()
'123'

があります。一方、ここで

>>> re.search('\d*', 'abc123xyz').group()
''

とすると空文字列が返ってきます。正規表現は最長一致を返すのにもかかわらず、なぜこうなるのかと質問が出ました。

.groups()re.findall()の違い

この挙動を理解するには正規表現のグループとマッチした文字列全体の違いについて知らなければなりません。理解しやすくするために問題を少し変えて

abc123xyz456

とします。すると、

>>> re.search('\d+', 'abc123xyz456').group()
'123'

となります。これはre.search()は最初にマッチした文字列全体2を返すためです。ここで、groups()もありますが、その結果は

>>> re.search('\d+', 'abc123xyz456').groups()
()

と何もありません。それもそうで、正規表現中でグループ(())を指定していないためです。そこで、\d+をグループで括ってやると、

>>> re.search('(\d+)', 'abc123xyz456').groups()
('123',)

と得られます。これだとグループの恩恵がわかりづらいので、数字の前のアルファベットもグループで括ると、

>>> re.search('([A-Za-z]+)(\d+)', 'abc123xyz456').groups()
('abc', '123')

となります。ここでgroup()には引数を渡すことができ、

>>> re.search('([A-Za-z]+)(\d+)', 'abc123xyz456').group(1)
'abc'
>>> re.search('([A-Za-z]+)(\d+)', 'abc123xyz456').group(2)
'123'
>>> re.search('([A-Za-z]+)(\d+)', 'abc123xyz456').group() # .group(0)と同じ
'abc123'

と引数でグループの何番目の文字列を得るかを指定できます。引数を与えない場合のデフォルトが0であり、これはマッチした文字列全体を返します。

一方、.groups()と混同しやすいre.findall()では何を返すかというと、

>>> re.findall('([A-Za-z]+)(\d+)', 'abc123xyz456')
[('abc', '123'), ('xyz', '456')]

マッチした文字列全体すべて見つけて返します。

つまり、.groups()は最初にマッチした文字列全体の中でグループに分けて返し、一方re.findall()すべてのマッチした文字列全体をそれぞれグループに分けて返すという違いがあります。

冒頭の問題の答え

re.findall()をすれば理由は明らかとなります。

>>> re.findall('\d+', 'abc123xyz')
['123']
>>> re.findall('\d*', 'abc123xyz')
['', '', '', '123', '', '', '', '']

つまり、\d*abの間にあるような空文字列に対しても最長一致でマッチした文字列全体と認識するので、re.search()は最初に見つかったマッチした文字列全体である''を返していたということになります。

続き(正規表現re.findall()と重複の落とし穴【Python】)


  1. まったく同じ内容をここに載せるのも問題なので、問題内容は変えている 

  2. (のマッチオブジェクト)