忍者ブログ
     2008年11月14日 開始
[26] [27] [28] [29] [30] [31] [32] [33] [34] [35] [36]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

http://www.python.jp/Zope/articles/tips/regex_howto/regex_howto_3
 

3.1. 正規表現のコンパイル

正規表現は RegexObject インスタンスにコンパイルされます。このインスタンスは、パターン・マッチや文字列交換のような、様々な働きをするメソッドを持ちます。

>>> import re
>>> p = re.compile('ab*')
>>> print p
<re.RegexObject instance at 80b4150>

re.compile() に引数 flags を指定することで、特別な設定や文法の変更を実現するのに使われます。使用可能な設定は後で見ていくことにして、ここでは例だけ挙げておきます。

>>> p = re.compile('ab*', re.IGNORECASE)

正規表現は re.compile() に文字列として渡されます。これは、正規表現は Python 本体には含まれていないので、正規表現を表すのに特化した文法が用意されていないからです。 (正規表現を全く必要としないアプリケーションも存在するので、正規表現を内包することによる言語仕様をふくらませる必要はありません) その代わり re モジュールは、string モジュールと同様、単なる C 拡張モジュールになっています。

3.2. バックスラッシュだらけ

既に述べたように、正規表現はバックスラッシュ文字("\")で、特殊な形式を表したり、特殊文字に特殊な意味を持たせないのに使います(訳注: 日本語フォントセットでは、バックスラッシュは円記号で表示されます)。この規則は、文字列リテラル中で Python がバックスラッシュを同じ目的で使用するのと衝突します。

LaTeX 文書で見られるような文字列 "\section" とマッチする正規表現を書きたいとしましょう。プログラムのソースコードの中で、何と書くか導くために、マッチさせたい文字列から考えます。次にバックスラッシュを前置して、バックスラッシュやメタ文字をエスケープしなければいけません。その結果、文字列 "\\section" を得ます。 re.compile() に渡されるべき文字列は、\\section でなければいけません。しかし、Python の文字列リテラルでこれを表現するには、両方のバックスラッシュを再度エスケープしなければならないのです。

文字列 段階
\section マッチさせるテキスト文字列
\\section re.compile 用にバックスラッシュをエスケープ
"\\\\section" 文字列リテラル用にバックスラッシュをエスケープ

つまり、リテラルのバックスラッシュにマッチさせるには、正規表現文字列として '\\\\' と書かなければいけません。なぜなら正規表現は "\\" でなくてはいけませんし、Python の文字列リテラル中で、各バックスラッシュは "\\" と書かれなければいけないからです。バックスラッシュが繰り返し現れる正規表現中では、これが多くのバックスラッシュの繰り返しを誘発することになり、その文字列を理解することが困難になります。

解決策は Python の生の文字列(raw string)表記を正規表現に使うことです。 "r" を前置した文字列リテラル内では、バックスラッシュは特別な扱いを受けません。ですから r"\n" は、"\" と "n"の 2 文字からなる文字列ですが、"\n" は 1 つの改行文字です。 Python のコードで正規表現を書くとき、この生の文字列表記がよく用いられます。

一般の文字列 生の文字列
"ab*" r"ab*"
"\\\\section" r"\\section"
"\\w+\\s+\\1" r"\w+\s+\1"

3.3. マッチングを行う

ここまでで、コンパイルされた正規表現を represent するオブジェクトを持っているのですが、これで何をすればいいのでしょう。 RegexObject インスタンスは、いくつかのメソッドと属性を持っています。最も重要なものだけをここで扱います。完全なリストを手に入れるには、ライブラリ・リファレンスをあたってください。

メソッド / 属性 目的
match 正規表現が、文字列の先頭でマッチするかどうか調べる
search 文字列を走査し、正規表現がマッチする位置を探す
split 正規表現がマッチする全ての位置で、文字列をリストに分割する
sub 正規表現がマッチする全ての部分文字列を見つけ出し、別の文字列に置換する
subn sub()と同じ。ただし置換の個数を制限することができる

全くマッチしなければ、これらのメソッドは None を返します。見つかれば MatchObject インスタンスを返します。このインスタンスは、マッチした部分文字列や、それがどこで始まり、どこで終わるか、などの情報をもっています。

re モジュールを使って、対話的に実験することで勉強していきましょう。 (Tkinter が使えるなら redemo.py を見るのもよいでしょう。これは Python ディストリビューションに含まれているデモです。正規表現を入力すると、マッチしたか失敗したかを表示してくれるのです。コンパイルされた正規表現をデバッグしようとするときには、redemo.py が非常に有用であると思われます)

最初に、Python インタプリタを起動して、re をインポートしてから、正規表現をコンパイルします。

Python 1.5.1 (#6, Jul 17 1998, 20:38:08)	[GCC 2.7.2.3] on linux2
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
<re.RegexObject instance at 80c3c28>

これで正規表現 [a-z]+ に対して、様々な文字列のマッチングを試みることができます。 + は「1 回または、それ以上の繰り返し」を意味するので、空の文字列はまったくマッチしないはずです。この場合 match() の戻り値 None はインタプリタに何の出力も表示させません。結果をはっきりさせたければ、明示的に match() の結果をを表示すこともできます。

>>> p.match("")
>>> print p.match("")
None

今度は、"tempo" のような、マッチする文字列を試してみましょう。この場合、match()MatchObject を返すので、後で使えるように、この結果を変数に保存しておきます。

>>> m = p.match('tempo')
>>> print m
<re.MatchObject instance at 80c4f68>

この MatchObject から、マッチした文字列に関する情報を得ることができます。 MatchObject インスタンスには、いくつかのメソッドと属性があります。そのうち特に重要なものは以下のとおりです。

メソッド / 属性 目的
group() 正規表現にマッチした文字列を返す
start() マッチした文字列が始まる位置を返す
end() マッチした文字列が終わる位置を返す
span() マッチした文字列の位置を (start, end) のタプルで返す

これらのメソッドを使ってみれば、どういうことか明確になるでしょう。

>>> m.group()
'tempo'
>>> m.start(), m.end()
(0, 5)
>>> m.span()
(0, 5)

group() は正規表現にマッチした部分文字列を返します。 start()end() は、マッチした部分の開始位置と終了位置を返します。 span() は開始位置と終了位置の両方を、タプルにして返します。 match メソッドは、正規表現が文字列の先頭でマッチするかどうかをチェックするだけなので、start() の戻り値は常に 0 です。しかし RegexObject インスタンスの search メソッドは、文字列全部に渡って調べるので、マッチの開始位置が 0 でない場合もあります。

>>> print p.match('::: message')
None
>>> m = p.search('::: message') ; print m
<re.MatchObject instance at 80c9650>
>>> m.group()
'message'
>>> m.span()
(4, 11)

実際のプログラムでは、MatchObject を変数に保存し、その値が None かどうかチェックするのが最も一般的なスタイルで、次のようなになります。

p = re.compile( ... )
m = p.match('string goes here')
if m:
    print 'Match found: ', m.group()
else:
    print 'No match'

3.4. モジュールレベルの関数

必ずしも RegexObjectを作ったり、そのメソッドを呼び出す必要はありません。 re モジュールは、match(), search(), sub(), などのトップレベル関数も提供します。これらの関数は、RegexObject メソッドと対応していて、第 1 引数が正規表現文字列であるような引数を取り、なおかつ None または MatchObject インスタンスを返します。

>>> print re.match(r'From\s+', 'Fromage amk')
None
>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')
<re.MatchObject instance at 80c5978>

この隠れ蓑の下で、これらの関数はあなたに代わって RegexObject を作り、それに対して適切なメソッドを呼び出すだけです。また、コンパイルされたオブジェクトをキャッシュに保存し、後で、同じ正規表現をを使うときの呼び出しが速くなります。

モジュールレベルの関数を使うべきでしょうか、RegexObject を作ってからメソッドを呼び出すべきでしょうか。どれだけ頻繁にその正規表現を使わうのか、あなたのコーディングスタイル次第です。もし正規表現がコードの中で、たった 1 箇所だけで使うのなら、モジュール関数がおそらく便利でしょう。一方、プログラムがたくさんの正規表現を包含していたり、同じ正規表現を別々の箇所で何度も使ったりするなら、全ての定義を始めの 1 箇所に集めて、正規表現をコンパイルする価値があるでしょう。標準ライブラリの xmllib.py を例にとってみましょう。

ref = re.compile( ... )
entityref = re.compile( ... )
charref = re.compile( ... )
starttagopen = re.compile( ... )

(私はふつう、1 回使うだけであっても、コンパイル済みオブジェクトを好みます。私と同じくらい、徹底するようになる人もいるようです)

3.5. コンパイル時のフラグ

コンパイル時にフラグを指定することで、正規表現が動作するときの振るまいを変更できます。 re モジュールのフラグには、IGNORECASE のようなロングネームと、Iのように1文字で表すショートネームが使えます。 (もし Perl のパターン修飾子に慣れているなら、ショートネームは Perl と同じ文字を使うと言えば分かりやすいかも知れません。 re.VERBOSE のショートネームは re.X です) 複数のフラグは、OR ビット演算で指定できます。たとえば、re.I | re.M は、IM の両方を指定することになります。

以下に使用できるフラグの表を示します。詳しい説明は後述します。

フラグ 意味
DOTALL, S . が改行も含めて、全ての文字とマッチするように指定する
IGNORECASE, I 大文字小文字を区別しない
LOCALE, L ロケールを考慮してマッチングを行う
MULTILINE, M 複数行にマッチング。これは ^$ に影響する
VERBOSE, X 冗長な正規表現(もっと、きれいで分かりやすくまとめられる表現)を有効にする。
I, IGNORECASE
大文字小文字を区別せずにマッチングを行う。文字クラスとリテラル文字列は大文字小文字を無視して、マッチングが行われる。例えば、[A-Z] は小文字にもマッチし、Spam は、"Spam"、"spam"、"spAM" にマッチします。このフラグ指定では、カレント・ロケールを考慮しません。
L, LOCALE
\w, \W, \b, \B をカレント・ロケールに依存して扱います。ロケールは、扱う自然言語の違いを考慮に入れるのを手助けする目的で、C 言語ライブラリに含まれる機能です。たとえば、フランス語を処理するときには、単語にマッチさせるのに \w+ と書けることを望むでしょう。しかし、\w は文字クラス [A-Za-z] のみとマッチし、"é" や "ç" とはマッチしません。システムが適切に設定されていて、フランス語が選択されていれば、特定の C 言語の関数が、プログラムに対して "é" も文字扱いするように教えるのです。正規表現をコンパイルするときに LOCALE フラグを設定すると、コンパイル済みオブジェクトが \w を扱うときに、こういう C 言語の関数を使うことになります。この場合処理は遅くなりますが、期待どおり \w+ がフランス語の単語にマッチします。

 

M, MULTILINE
通常、^ は行頭にのみ、$ は(もし存在すれば)newline 直前の行末にのみマッチします。このフラグを指定すると、^ は文字列の先頭と、文字列中で newline の直後に続く行頭にマッチします。同様に、メタ文字 $ は、文字列の最後と(newline 直前の)行末にマッチします。
S, DOTALL
特殊文字 "." が改行文字も含めて、あらゆる文字にマッチするようにします。このフラグを指定しないと、"." は改行文字以外のあらゆる文字にマッチします。
X, VERBOSE
このフラグを指定すると、柔軟な書式で、正規表現を読みやすく書くことができます。文字クラスやエスケープされていないバックスラッシュで生成された空白文字を除いて、正規表現中の空白文字は無視されます。これにより、正規表現を分かりやすくまとめたり、インデントしたりできるのです。また、正規表現中にコメントを入れることもできます。文字クラスや、エスケープされていないバックスラッシュによって生成されている場合を除き、"#" 以降がコメントになります。コメントは単に無視されます。

re.VERBOSE を使った正規表現の例を以下に示します。とても読みやすいと思いませんか?

charref = re.compile(r"""
  &#                            # 数値型の開始
  (?P<char>
        [0-9]+[^0-9]                # 10進法
        | 0[0-7]+[^0-7]             # 8進法
        | x[0-9a-fA-F]+[^0-9a-fA-F] # 16進法
)
""", re.VERBOSE)

冗長表現を指定しないと、この正規表現は次のようになります。

charref = re.compile("&#(?P<char>[0-9]+[^0-9]"
"|0[0-7]+[^0-7]"
"|x[0-9a-fA-F]+[^0-9a-fA-F])")

上の例では、Python が文字列リテラルを自動的に連結するのを利用して、正規表現を小さな部分に分割していますが、それでも re.VERBOSE を使った場合に比べて、分かりにくくなります。

PR


忍者ブログ [PR]
お天気情報
カレンダー
10 2024/11 12
S M T W T F S
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
リンク
フリーエリア
最新CM
最新TB
プロフィール
HN:
No Name Ninja
性別:
非公開
バーコード
ブログ内検索
P R
カウンター
ブログの評価 ブログレーダー