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

Pythonで入れ子になっている括弧内をマッチさせようと思ってググったら、
[正規表現][Python]入れ子括弧にマッチする。
というブログ記事が引っかかったのですが、これだと上手くいかなかったので書いておきます。

例えば、

data = """{info
a={hoge}
b={fuga}
c=1}
{otherinfo
d=2
e={piyo}}"""

のようなデータがあった時、

{info
a={hoge}
b={fuga}
c=1}

の部分にマッチさせたいとします。
普通の正規表現だと、このように入れ子になっている括弧の中身を取得するのは困難です。
例えば、

# ダメな例
import re
pattern = r"\{.+\}"
m = re.search(pattern, data, re.DOTALL)
print m.group(0)

のようにすると.が貪欲マッチになって

{info
a={hoge}
b={fuga}
c=1}
{otherinfo
d=2
e={piyo}}

のように全てとれてしまうし、

# ダメな例
import re
pattern = r"\{.+?\}"
m = re.search(pattern, data, re.DOTALL)
print m.group(0)

のようにすると、最小一致マッチになって

{info
a={hoge}

のように変なところで途切れてしまいます。

こういう時は、再帰を使うと上手くできるようです。
Python標準の正規表現モジュールであるreは再帰をサポートしていないので、
reの拡張であるregexを使います。

インストール方法は最新版をダウンロードしてきて、

$ tar zxvf regex-2015.03.18.tar.gz
$ cd regex-2015.03.18
$ sudo python setup.py install

です。
これを入れたら

import regex
pattern = r"(?<rec>\{(?:[^{}]+|(?&rec))*\})"
m = regex.search(pattern, data, flags=regex.VERBOSE)
print m.group('rec')

のようにすれば、

{info
a={hoge}
b={fuga}
c=1}

この通り、欲しかった所が取れました。

パターンの説明をすると、
一番外側の(?<rec>〜〜〜)は、全体をグループ化してグループにrecという名前をつけています。
その次の\{は始まりの括弧({)をマッチさせます。
次の (?:〜〜〜)はグループ化するけど、キャプチャしないの意味です。
[^{}]+は括弧({と})以外の文字のが1回以上連続するところにマッチして、|はORです。
はさっき名前をつけた自分自身です。
\}は閉じ括弧にマッチします。

つまり、自分自身のパターンにrecと名前をつけて、recは、
1. 中身に{}という文字を含まないものが、{}で囲まれている場合 ({hoge}のようなもの)
または
2. recでマッチできるものが、{}で囲まれている場合
にマッチするので、自分自身のパターンを再帰的に含む (部分式呼び出し) ことで、括弧の対応が取れた状態でマッチさせられるようになります。

※こういう場合は、構文解析器を使うのが普通なのかもしれませんが、よく知りません

補足

言語処理100本ノック 2015の問25でこれが使えると思います。

参考

Python: How to match nested parentheses with regex? – Stack Overflow

You may also like...

コメントを残す

メールアドレスが公開されることはありません。