質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

1回答

5739閲覧

正規表現でのm.group()の()の数字は何を指定しているのかわかりません。

gunmed

総合スコア55

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2019/02/06 05:01

正規表現の勉強をしています。
そこで以下のコードを書いて実行しました。

import re content = '"You say", "Yes, I am.".' m = re.search('"(.*)"', content) print(m.group(0)) print(m.group(1))

結果はこちら

"You say", "Yes, I am." You say", "Yes, I am.

とりあえず指示にしたがって入力してみましたが、以下のことが疑問になってしまいました。
①'"(.*)"'の()は必要なのか?
()に対し何も説明がなく、参考書をみてみたのですが、当たり前のように使っていました。確かに除くとエラーがでるので、必要だとは思うのですが。色々調べてグループ化の()と思うのですが、グループ化しなくても.*だけで任意の文字列を表しているので、()は必要ないのではと思いました。

②m.groupについてですが、これは検索結果の文字列を返すのですが、()の中の指定した数字で何が表示されるのかがいまいち理解できていません。

(0)はマッチした文字列全体と学んでいたので''で囲まれた文字列が(0)で表示されるのはわかります。
(1)では1番目のグループ(最初の括弧内)が表示されると学んだので、はじめの"と最後の"で囲まれた文字列You say", "Yes, I am.が表示されるのもわかります。
しかし、(2)だと次はsayの後の"と、Yesの前の"の文字列", "の中身が表示されるのかと思ったらエラーが出てしまいました。

"You say", "Yes, I am." You say", "Yes, I am. Traceback (most recent call last): File "re2.py", line 8, in <module> print(m.group(2)) IndexError: no such group

さらに、いろんなサイトをあさって勉強していたのですが、
そこで他のサイト(https://qiita.com/kaeruair/items/747518c116c85a88ee21)で以下のものを見つけました。

import re m = re.search(r"([a-z]+)\s*=\s*([0-9]+)", "index = 123") if m: print(m.group(0)) # -> "index = 123" print(m.group(1)) # -> "index" print(m.group(2)) # -> "123"

(0)は文字列全体を表示するのはわかりますが、なぜ(1)がindex、(2)が123を表示するのかがわかりません。
文字列には""が一つしかなく、先ほどの(n)ではn番目のグループ(n番目の括弧)を表示するという理解では矛盾してしまいます。
つまり(1)も(2)も存在しないのではと思いました。

以上のような矛盾が生じていて、自分のgroup()の理解が間違っていると思うのですが、その辺を中心に詳しく教えていただけたら幸いです。長文失礼しました。よろしくお願いします。

mac 10.14.1

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

()の意味について

色々調べてグループ化の()と思うのですが、グループ化しなくても.*だけで任意の文字列を表しているので、()は必要ないのではと思いました。

()にはグループ化の他に、キャプチャの意味があります。


例えば、次のような文字列にマッチさせるとしましょう。(註)

He is my friend, John

名前の部分だけ可変で、後は固定の文字列とします。
全体にマッチする正規表現を書くのは簡単で、He is my friend, .+とでもすれば良いです。

Python

1>>> src = """He is my friend, John""" 2>>> m = re.match(r'He is my friend, .+', src) 3>>> 4>>> print(m.group()) 5He is my friend, John 6>>> print(m.group(0)) 7He is my friend, John

確かにマッチできていますね。

しかしこのようなケースでは、名前部分だけ取り出したいという要求が生じがちです。
キャプチャを用いれば、文字列を部分的に切り出すことが簡単にできます。

Python

1>>> m = re.match(r'He is my friend, (.+)', src) 2>>> print(m.group(0)) 3He is my friend, John 4>>> print(m.group(1)) 5John

その他にも、後方参照に用いることもあります。
以下の例は**『同じワードを二回だけ繰り返している文字列』**にマッチします。

Python

1>>> m = re.match(r'(.+) \1', 'spam spam') 2>>> print(m) 3<_sre.SRE_Match object; span=(0, 9), match='spam spam'> 4>>> 5>>> m = re.match(r'(.+) \1', 'spam ham') 6>>> print(m) 7None 8>>> 9>>> m = re.match(r'(.+) \1', 'ham ham') 10>>> print(m) 11<_sre.SRE_Match object; span=(0, 7), match='ham ham'>

以下の例は、置換にキャプチャを利用しているものです。

Python

1>>> src = 'Check, 123' 2>>> 3>>> re.sub(r'(\d+)', r'\1', src) 4'Check, 123' 5>>> re.sub(r'(\d+)', r'\1\1', src) 6'Check, 123123' 7>>> re.sub(r'(\d+)', r'\1\1\1', src) 8'Check, 123123123'

註: 本来なら正規表現で解決するタスクでは無いですが、あくまで簡単な例として。

re.searchの挙動

しかし、(2)だと次はsayの後の"と、Yesの前の"の文字列", "の中身が表示されるのかと思ったらエラーが出てしまいました。

誤解されている点がいくつかあります。

  • 正規表現は広い範囲にマッチした後、部分にマッチすることはありません。

もしそんな挙動だと、.+ のマッチ件数がとんでもないことになります。

Python

1>>> m = re.search(r'"(.+?)", "(.+?)"', content) 2>>> m.groups() 3('You say', 'Yes, I am.') 4>>> 5>>> m.group(0) 6'"You say", "Yes, I am."' 7>>> m.group(1) 8'You say' 9>>> m.group(2) 10'Yes, I am.'

結局広い範囲に一回だけマッチしているに過ぎません。
groupはその際にキャプチャした部分マッチを取り出しているだけです。

  • re.searchはマッチした際に検索を打ち切ります。

一度マッチすれば、それ以上追って検索することはありません。
マッチする全ての部分文字列を得たいときには、re.findallやre.finditerを使います。

Python

1>>> re.findall(r'"(.+?)"', content) 2['You say', 'Yes, I am.']

ぶっちゃけこっちの方がご提示の例には即していると思います。

発展

さりげなく (.+?) という正規表現を何回か使っていますが、これは最左最短マッチです。
単に最短マッチと呼ぶこともありますし、非貪欲マッチと呼ばれることもあります。

これについては、Pythonのリファレンスに詳しいです。

######*?, +?, ??

'' 、 '+' 、 '?' といった修飾子は、すべて 貪欲 (greedy) マッチ、すなわちできるだけ多くのテキストにマッチするようになっています。時にはこの動作が望ましくない場合もあります。例えば正規表現 <.> を '<a> b <c>' にマッチさせると、 '<a>' だけにマッチするのではなく全文字列にマッチしてしまいます。 ? を修飾子の後に追加すると、 非貪欲 (non-greedy) あるいは 最小一致 (minimal) のマッチになり、できるだけ 少ない 文字数のマッチになります。例えば正規表現 <.*?> を使うと '<a>' だけにマッチします。

引用元: Python 標準ライブラリ » 正規表現のシンタックス


**『グループ化はしたいけどキャプチャはしたくない』**というときも往々にしてありますが、
そのようなときは (?:正規表現) を使うことができます。

Python

1>>> m = re.match(r'(\d{2})+', '343434') 2>>> print(m) 3<_sre.SRE_Match object; span=(0, 6), match='343434'> 4>>> print(m.groups()) 5('34',) 6>>> 7>>> m = re.match(r'(?:\d{2})+', '343434') 8>>> print(m) 9<_sre.SRE_Match object; span=(0, 6), match='343434'> 10>>> print(m.groups()) 11()

これはPythonの正規表現拡張です。元はPerlの拡張だったと思います。

投稿2019/02/06 05:17

編集2019/02/06 05:52
LouiS0616

総合スコア35658

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Stan_Dma

2019/02/06 05:30

Capturing is also useful to replace strings with it.
LouiS0616

2019/02/06 05:31

実は既に追記してあったりします。ページを更新してご確認ください。
gunmed

2019/02/06 10:42

回答ありがとうございました。 m.groupの数字は0は全体を、それ以降はキャプチャの順で取り出すのですね。 そう理解して以下のようなものを実行すると import re src = src = 'He, aaa, is my own friend, John' m = re.match(r'He, (.+), is my own friend, (.+)', src) print(m.group(0)) print(m.group(1)) print(m.group(2)) 無事(1)でaaa、(2)でJohnが出力されました。 re.findallやre.finditerも勉強になりました。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問