正規表現あれこれ

この記事は Python Tips Advent Calendar 2012 24日目の記事です。


Python正規表現には、便利な機能がいくつかあります。ということで、いくつか見ていきましょう。

コメント機能

>>> import re
>>> re.compile(r'''
...     ^              # start of a line
...     \[font         # the font tag
...     (?:=(?P<size>  # optional [font=+size]
...     [-+][0-9]{1,2} # size specification
...     ))?
...     \]             # end of tag
...     (.*?)          # text between the tags
...     \[/font\]      # end of the tag
... ''', re.VERBOSE) #doctest: +ELLIPSIS
<_sre.SRE_Pattern object at 0x...>

このように VERBOSE フラグを付けるとコメントが付けらます。
VERBOSE フラグは、空白と # 以降の文字列を無視するようになります。もし空白や # を使いたい場合は \# のようにエスケープする必要があります。

デバッグモード

>>> import re
>>> re.compile(r'^\[font(?:=(?P<size>[-+][0-9]{1,2}))?\](.*?)[/font]', re.DEBUG) #doctest: +ELLIPSIS
at at_beginning
literal 91
literal 102
literal 111
literal 110
literal 116
max_repeat 0 1
  subpattern None
    literal 61
    subpattern 1
      in
        literal 45
        literal 43
      max_repeat 1 2
        in
          range (48, 57)
literal 93
subpattern 2
  min_repeat 0 65535
    any None
in
  literal 47
  literal 102
  literal 111
  literal 110
  literal 116
<_sre.SRE_Pattern object at 0x...>

このように、どんな状態機械になったのかが標準出力に出力されるので、デバッグが容易になります。

置換時に関数を渡す

sub 関数を使って置換する場合、通常は文字列を指定しますが、関数を指定することもできます。

>>> import re
>>> re.sub('ab|bb', \
...        lambda m: 'xxx' if m.group(0) == 'ab' else 'yyy', \
...        'aaabbb')
'aaxxxyyy'

このように関数を渡すと、文字列がマッチするたびに関数が呼ばれます。
引数には MatchObject が渡され、どの文字列に一致したかなどの情報が取れます。

複数の文字列置換

sub 関数は、例えば複数の文字列を置換する場合に役立ちます。
例えば html のエスケープなどで普通に replace 関数を使った場合、以下の様な問題に遭遇することがあります。

>>> text = '<hoge>&&&</hoge>'
>>> text.replace('<', '&lt;') \
...     .replace('>', '&gt;') \
...     .replace('&', '&amp;')
'&amp;lt;hoge&amp;gt;&amp;&amp;&amp;&amp;lt;/hoge&amp;gt;'

'<' を '&lt;' に置換した後、'&' を '&amp;' に置換しているので '&amp;lt;' になっています。これは意図した動作では無いでしょう。
この場合であれば '&' の置換を先頭にやれば一応動作はしますが、そのような順序に依存した変換はバグが発生しやすくなってしまいます。


これは sub の引数に関数を指定することで簡単に一括で置換できます。

>>> def multiple_replace(text, dic):
...     if len(dic) == 0:
...         return text
...     import re
...     return re.sub('|'.join(map(re.escape,  dic)),
...                   lambda m: dic[m.group(0)],
...                   text)
>>> multiple_replace('<hoge>&&&</hoge>', {
...     '<': '&lt;',
...     '>': '&gt;',
...     '&': '&amp;' })
'&lt;hoge&gt;&amp;&amp;&amp;&lt;/hoge&gt;'

それぞれの置換対象の文字列を '|' で繋いでどれかにマッチさせるようにして、そのマッチした文字列からどの文字列に置換したいかを指定しています。


Python正規表現はちゃんと使うとかなり便利になるので、一度は re のドキュメントを全部読んでおいた方がいいでしょう。


.