HYT MachineWorks

やったこととか思いついたことをメモしておくブログです。

Pythonの正規表現で、括弧に囲まれた括弧を取る方法

カッコに囲まれたカッコとは

<あか<abcdef>さたな><はま<abcdef>やらわ>

みたいな文から

  1. <あか<abcdef>さたな>
  2. <はま<abcdef>やらわ>

を取り出したいと言うことです。簡単そうに見えてハマったのでメモ。

 色々と試してみる

普通に<と>に囲まれた文字考えると

import re
text = "<あ<a>か<b>さ<c>た<d>な><は<e>ま<f>や<g>ら<h>わ>"
re_pattern = r"<.*>"
re_text = re.search(re_pattern, text)
print(re_text.group())
>>>><あ<a>か<b>さ<c>た<d>な><は<e>ま<f>や<g>ら<h>わ>

全部取ってしまいます。これは、Pythonのデフォルトが貪欲マッチになっているからで手前でマッチしたとしても最後にマッチするところまでマッチさせてしまいます。

参考:正規表現 HOWTO — Python 3.6.1 ドキュメント

逆に貪欲じゃなくするには*のあとに?をつけるといいのでつけてみると

import re
text = "<あ<a>か<b>さ<c>た<d>な><は<e>ま<f>や<g>ら<h>わ>"
re_pattern = r"<.*?>"
re_text = re.search(re_pattern, text)
print(re_text.group())
>>>><あ<a>

ちょ・・まじで、はじめにマッチで終わってしまう・・・

で、頭を抱えてググったら解決してくれるページを見つけました。

Pythonの正規表現で入れ子括弧内をうまくマッチさせる方法 – NKNK

そのまんまですね。参考に変更すると

import regex
text = "<あ<a>か<b>さ<c>た<d>な><は<e>ま<f>や<g>ら<h>わ>"
re_pattern = r"(?<rec><(?:[^<>]+|(?&rec))*>)"
re_text = regex.search(re_pattern, text)
print(re_text.group())

バッチリできたのですが、正規表現の部分の自分の理解の確認の為に説明。

1.入れ子にするような条件を行うにはモジュール regexを使う。

 これは、reを拡張するモジュールで、上で上げたpython正規表現ドキュメントでも紹介されている。インストールは、conda でも pipでも入るのでお好みで

2.正規表現の構造

  1. (?<rec><.....>) の外側の部分で<と>に挟まれたパターンに「rec」と命名。
  2. (?:...)は、その中のパターンにマッチしてもマッチを終了しない。(このパターンは、先読みをするので読んでる位置よりも先の状態まで考慮するので中に入っているかどうかを判断できる。)
  3. そして、その終了しない条件は、<と>以外の文字もしくは、recのパターン(<と>に挟まれた文字)に該当するもの。

という感じで取っていると思ってる。

参考にしたサイトにも書いてあるけど、自然言語100本ノックの問25で使えますというか使いました。