6.2. re
— 正規表現操作¶
このモジュールでは、Perl などと同様の正規表現マッチング操作を提供しています。
パターンと文字列には Unicode 文字列および 8 ビット文字列が使用できます。しかし、Unicode 文字列と8ビット文字列の混在はできません。すなわち、Unicode 文字列をバイト列のパターンでマッチングや、その逆はできません。同様に、置き換え時の置換文字列はパターンおよび検索文字列と同じ型でなくてはなりません。
正規表現では、特殊な形式を表したり、特殊文字の持つ特別な意味を呼び出さずにその特殊な文字を使えるようにするために、バックスラッシュ文字 ('\'
) を使います。こうしたバックスラッシュの使い方は、Python の文字列リテラルにおける同じバックスラッシュ文字と衝突を起こします。例えば、バックスラッシュ自体にマッチさせるには、パターン文字列として '\\\\'
と書かなければなりません、というのも、正規表現は \\
でなければならず、さらに正規な Python 文字列リテラルでは各々のバックスラッシュを \\
と表現しなければならないからです。
正規表現パターンに Python の raw 文字列記法を使えばこの問題を解決できます。'r'
を前に付けた文字列リテラル内ではバックスラッシュを特別扱いしません。従って、"\n"
が改行一文字の入った文字列になるのに対して、r"\n"
は '\'
と 'n'
という二つの文字の入った文字列になります。通常、Python コード中では、パターンをこの文字列記法を使って表現します。
大抵の正規表現操作が、モジュールレベルの関数と、 コンパイル済み正規表現 のメソッドとして提供されることに注意して下さい。関数は正規表現オブジェクトのコンパイルを必要としない近道ですが、いくつかのチューニング変数を失います。
参考
- Mastering Regular Expressions (邦題『詳説 正規表現』)
Jeffrey Friedl 著、O’Reilly 刊の正規表現に関する書籍です。この本の第2版では Pyhon については触れていませんが、良い正規表現パターンの書き方を非常に詳しく説明しています。
6.2.1. 正規表現の構文¶
正規表現 (RE) は、表現にマッチする文字列の集合を表しています。このモジュールの関数を使えば、ある文字列が指定の正規表現にマッチするか (または指定の正規表現がある文字列にマッチするか、つまりは同じことですが) を検査できます。
正規表現を連結すると新しい正規表現を作れます。A と B がともに正規表現であれば AB も正規表現です。一般的に、文字列 p が A とマッチし、別の文字列 q が B とマッチすれば、文字列 pq は AB にマッチします。ただし、この状況が成り立つのは、A と B との間に境界条件がある場合や、番号付けされたグループ参照のような、優先度の低い演算を A や B が含まない場合だけです。このようにして、ここで述べるような、より簡単でプリミティブな正規表現から、複雑な正規表現を容易に構築できます。正規表現に関する理論と実装の詳細については上記の Friedl 本か、コンパイラの構築に関するテキストを参照してください。
以下で正規表現の形式に関する簡単な説明をします。より詳細な情報やよりやさしい説明に関しては、正規表現 HOWTO を参照下さい。
正規表現には、特殊文字と通常文字の両方を含めることができます。'A'
、'a'
、あるいは '0'
のようなほとんどの通常文字は最も簡単な正規表現になります。こうした文字は、単純にその文字自体にマッチします。通常の文字は連結できるので、last
は文字列 'last'
とマッチします。(この節の以降の説明では、正規表現を引用符を使わずに この表示スタイル: special style
で書き、マッチ対象の文字列は、'引用符で括って'
書きます。)
'|'
や '('
といったいくつかの文字は特殊文字です。特殊文字は通常の文字の種別を表したり、あるいは特殊文字の周辺にある通常の文字に対する解釈方法に影響します。正規表現パターン文字列には、null byte を含めることができませんが、\number
記法や、'\x00'
などとして指定することができます。
特殊文字を以下に示します:
'.'
(ドット) デフォルトのモードでは改行以外の任意の文字にマッチします。
DOTALL
フラグが指定されていれば改行も含むすべての文字にマッチします。'^'
(キャレット) 文字列の先頭とマッチします。
MULTILINE
モードでは各改行の直後にマッチします。'$'
文字列の末尾、あるいは文字列の末尾の改行の直前にマッチします。例えば、
foo
は ‘foo’ と ‘foobar’ の両方にマッチします。一方、正規表現foo$
は ‘foo’ だけとマッチします。興味深いことに、'foo1\nfoo2\n'
をfoo.$
で検索した場合、通常のモードでは ‘foo2’ だけにマッチし、MULTILINE
モードでは ‘foo1’ にもマッチします。$
だけで'foo\n'
を検索した場合、2箇所 (内容は空) でマッチします: 1つは、改行の直前で、もう1つは、文字列の最後です。'*'
直前にある RE に作用して、RE を 0 回以上できるだけ多く繰り返したものにマッチさせるようにします。例えば
ab*
は ‘a’、’ab’、あるいは ‘a’ に任意個数の ‘b’ を続けたものにマッチします。'+'
直前にある RE に作用して、RE を、1 回以上繰り返したものにマッチさせるようにします。例えば
ab+
は ‘a’ に一つ以上の ‘b’ が続いたものにマッチし、’a’ 単体にはマッチしません。'?'
直前にある RE に作用して、RE を 0 回か 1 回繰り返したものにマッチさせるようにします。例えば
ab?
は ‘a’ あるいは ‘ab’ にマッチします。*?
,+?
,??
'*'
、'+'
、'?'
といった修飾子は、すべて 貪欲 (greedy) マッチ、すなわちできるだけ多くのテキストにマッチするようになっています。時にはこの動作が望ましくない場合もあります。例えば正規表現<.*>
を'<H1>title</H1>'
にマッチさせると、'<H1>'
だけにマッチするのではなく全文字列にマッチしてしまいます。'?'
を修飾子の後に追加すると、非貪欲 (non-greedy) あるいは 最小一致 (minimal) のマッチになり、できるだけ 少ない 文字数のマッチになります。例えば上の式で.*?
を使うと'<H1>'
だけにマッチします。{m}
前にある RE の m 回の正確なコピーとマッチすべきであることを指定します; マッチ回数が少なければ、RE 全体ではマッチしません。例えば、
a{6}
は、正確に 6 個の'a'
文字とマッチしますが、5 個ではマッチしません。{m,n}
結果の RE は、前にある RE を、m 回から n 回まで繰り返したもので、できるだけ多く繰り返したものとマッチするように、マッチします。例えば、
a{3,5}
は、3個から 5個の'a'
文字とマッチします。m を省略するとマッチ回数の下限として0を指定した事になり、n を省略することは、上限が無限であることを指定します;a{4,}b
はaaaab
や、1,000 個の'a'
文字にb
が続いたものとマッチしますが、aaab
とはマッチしません。コンマは省略できません、省略すると修飾子が上で述べた形式と混同されてしまうからです。{m,n}?
結果の RE は、前にある RE の m 回から n 回まで繰り返したもので、できるだけ 少なく 繰り返したものとマッチするように、マッチします。これは、前の修飾子の控え目バージョンです。例えば、6 文字文字列
'aaaaaa'
では、a{3,5}
は、5 個の'a'
文字とマッチしますが、a{3,5}?
は 3 個の文字とマッチするだけです。'\'
特殊文字をエスケープする (
'*'
や'?'
等のような文字とのマッチをできるようにする) か、あるいは、特殊シーケンスの合図です; 特殊シーケンスは後で説明します。もしパターンを表現するのに raw 文字列を使用していないのであれば、Python も、バックスラッシュを文字列リテラルでのエスケープシーケンスとして使っていることを覚えておいて下さい; もしエスケープシーケンスを Python の構文解析器が認識して処理しなければ、そのバックスラッシュとそれに続く文字は、結果の文字列にそのまま含まれます。しかし、もし Python が結果のシーケンスを認識するのであれば、バックスラッシュを 2 回繰り返さなければなりません。このことは複雑で理解しにくいので、最も簡単な表現以外は、すべて raw 文字列を使うことを強く推奨します。
[]
文字の集合を指定するのに使用します。集合には以下のものが指定できます:
個別に指定できる文字。
[amk]
は'a'
、'm'
、または'k'
とマッチします。連続した文字の範囲を、先頭と最後の2文字とその間に
'-'
を挟んだ形で指定できます。[a-z]
はすべての小文字の ASCII 文字とマッチします。[0-5][0-9]
は00
から59
までの、すべての 2 桁の数字とマッチします。[0-9A-Fa-f]
はすべての 16 進数とマッチします。-
が、エスケープされた場合 (例:[a\-z]
)、あるいは先頭か末尾に置かれた場合 (例:[a-]
)、リテラル'-'
とマッチします。集合内では、特殊文字はその意味を失います。
[(+*)]
はリテラル文字'('
、'+'
、'*'
、あるいは')'
のいずれかとマッチします。\w
や\S
のような文字クラス (後述) も集合内に指定できますが、それらにマッチする文字はASCII
かLOCALE
のどちらか有効にされているモードに依存します。範囲内にない文字とは、その集合の 補集合 をとることでマッチできます。集合の最初の文字が
'^'
の時、集合に ない 文字すべてとマッチします。[^5]
は'5'
を除くあらゆる文字にマッチします。[^^]
は'^'
を除くあらゆる文字にマッチします。^
は集合の最初の文字でない限り特別の意味を持ちません。集合内でリテラル
']'
をマッチさせるには、その前にバックスラッシュをつけるか、集合の先頭に置きます。[()[\]{}]
と[]()[{}]
はどちらも']'
にマッチします。
'|'
A|B
(A と B は任意の RE) は、A か B のどちらかとマッチする正規表現を作成します。任意個数の RE を、このように'|'
で分離することができます。これはグループ (下記参照) 内部でも使えます。走査対象文字列をスキャンする中で、'|'
で分離された RE は左から右への順に走査されます。一つでも完全にマッチしたパターンがあれば、そのパターン枝が受理されます。このことは、もしA
がマッチすれば、たとえB
によるマッチが全体としてより長いマッチになったとしても、B
を決して走査しないことを意味します。言いかえると、'|'
演算子は決して貪欲 (greedy) にはなりません。リテラル'|'
とマッチするには、\|
を使うか、あるいは[|]
のように文字クラス内に入れます。(...)
丸括弧の中にどのような正規表現があってもマッチし、丸括弧はグループの開始と終了を表します; グループの中身は、マッチが実行された後に回収され、特殊シーケンス
\number
(後述) で参照することができます。リテラル'('
や')'
とマッチするには、\(
や\)
を使うか、それらを文字クラス内に入れます:[(] [)]
。(?...)
これは拡張記法です (
'('
に続く'?'
は他に意味がありません)。'?'
の後の最初の文字が、この構造の意味とこれ以上の構文がどういうものかを決定します。拡張記法は通常新しいグループを作成しません;(?P<name>...)
がこの規則の唯一の例外です。以下に現在サポートされている拡張記法を示します。(?aiLmsux)
(集合
'a'
,'i'
,'L'
,'m'
,'s'
,'u'
,'x'
から1文字以上)。グループは空文字列ともマッチします; 文字は、正規表現全体の対応するフラグre.A
(ASCII 限定マッチ)、re.I
(大文字・小文字を区別しない)、re.L
(ロケール依存)、re.M
(MULTILINE モード)、re.S
(DOTALL モード)、およびre.X
(冗長) を設定します (フラグについては モジュールの内容 を参照)。これは、flag 引数をre.compile()
関数に渡すのではなく、そのフラグを正規表現の一部に含めたい場合に役立ちます。(?x)
フラグは、表現の解析方法を変更することに注意して下さい。これは表現文字列内の最初か、あるいは 1 つ以上の空白文字の後で使うべきです。このフラグの前に非空白文字がある場合の結果は定義されていません。(?:...)
正規表現の丸括弧をキャプチャしないバージョンです。丸括弧内にどのような正規表現があってもマッチしますが、グループにマッチされた部分文字列は、マッチを実行したあと回収することも、後でパターンで参照することも できません 。
(?P<name>...)
正規表現の丸括弧に似ていますが、グループによってマッチした部分文字列はシンボリックグループ名 name によってアクセス可能になります。グループ名は有効な Python 識別子でなければならず、グループ名は 1 個の正規表現内で一意でなければなりません。シンボリックグループは番号付けもされており、番号によるアクセスも可能です。
名前付きグループは 3 つのコンテキストで参照できます。パターンが
(?P<quote>['"]).*?(?P=quote)
(シングルまたはダブルクオートのどちらかにマッチ) の場合`:グループ “quote” を参照するコンテキスト
参照方法
同一パターンへの参照
(?P=quote)
(そのまま)\1
マッチオブジェクト
m
の処理時m.group('quote')
m.end('quote')
(etc.)
re.sub()
のrepl
属性へ渡される文字列\g<quote>
\g<1>
\1
(?P=name)
名前付きグループへの後方参照です; 既出のグループ名 name にマッチする文字列は何にでもマッチします。
(?#...)
コメントです; 括弧の内容は無視されます。
(?=...)
...
が次に続くものとマッチすればマッチしますが、文字列をまったく消費しません。これは先読みアサーション (lookahead assertion) と呼ばれます。例えば、Isaac (?=Asimov)
は、'Isaac '
に'Asimov'
が続く場合だけ、'Isaac '
とマッチします。(?!...)
...
が次に続くものとマッチしなければマッチします。これは否定先読みアサーション (negative lookahead assertion) です。例えば、Isaac (?!Asimov)
は、'Isaac '
に'Asimov'
が続か ない 場合のみマッチします。(?<=...)
文字列内の現在位置の前に、現在位置で終わる
...
とのマッチがあれば、マッチします。これは 後読みアサーション と呼ばれます。(?<=abc)def
はabcdef
にマッチを見つけます。後読みは 3 文字をバックアップし、含まれているパターンとマッチするかどうか検査します。含まれるパターンは、固定長の文字列にのみマッチしなければなりません。すなわち、abc
やa|b
は許されますが、a*
やa{3,4}
は許されません。肯定後読みアサーションで始まるパターンは、検索される文字列の先頭とは決してマッチしないことに注意して下さい; この表現を使用するのは、おそらくmatch()
関数よりsearch()
関数の方が適しています:>>> import re >>> m = re.search('(?<=abc)def', 'abcdef') >>> m.group(0) 'def'
この例ではハイフンに続く単語を探します:
>>> m = re.search('(?<=-)\w+', 'spam-egg') >>> m.group(0) 'egg'
(?<!...)
文字列内の現在位置の前に
...
とのマッチがない場合に、マッチします。これは 否定後読みアサーション(negative lookbehind assertion) と呼ばれます。肯定後読みアサーションと同様に、含まれるパターンは固定長さの文字列だけにマッチしなければなりません。否定後読みアサーションで始まるパターンは、検索される文字列の先頭とマッチできます。(?(id/name)yes-pattern|no-pattern)
与えられたグループ id あるいは name が存在する場合、
yes-pattern
とのマッチを、存在しない場合no-pattern
とのマッチを試みます。no-pattern
は省略可能です。例えば、(<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$)
は貧弱な E-mail マッチングパターンで、'<user@host.com>'
や'user@host.com'
にマッチしますが、'<user@host.com'
や'user@host.com>'
とはマッチしません。
特殊シーケンスは '\'
と以下のリストの文字から構成されます。リストにない通常文字の場合 RE は 2 番目の文字とマッチします。例えば、\$
は文字 '$'
とマッチします。
\number
同じ番号のグループの内容にマッチします。グループは 1 から始まる番号です。例えば、
(.+) \1
は'the the'
や'55 55'
にはマッチしますが、'thethe'
(グループの後にスペースがない) にはマッチしません。この特殊シーケンスは最初の 99 グループのうちの一つにのみ使用できます。number 最初の数字が 0 の場合、あるいは number が 3 桁の 8 進数長だった場合、それらはグループとは認識されず、8 進数の値 number と認識されます。文字クラスの'['
と']'
の内側にある場合、すべてのエスケープされた数字は文字として扱われます。\A
文字列の先頭だけにマッチします。
\b
空文字列とマッチしますが、単語の先頭か末尾の時だけです。単語とは Unicode 英数字 (日本語など英字以外も含む非記号) またはアンダースコアからなるシーケンスで、単語の終わりは空白文字、あるいはアンダースコアを除く記号で表します。
\\b
は\\w
および\\W
の間 (およびその逆) あるいは\\w
と文字列の開始/終了との間の境界として定義されています。例えば、r'\\bfoo\\b'
は'foo'
、'foo.'
、'(foo)'
、'bar foo baz'
にマッチしますが、'foobar'
や'foo3'
にはマッチしません。デフォルトでは、Unicode 英数字 (日本語など英字以外も含む非記号) が使用されますが、これは
ASCII
フラグを使って ASCII に限定できます。文字範囲内では、\\b
は Python の文字列リテラルとの互換性のため、後退 (backspace) 文字を表します。\B
空文字列とマッチしますが、それが単語の先頭あるいは末尾に ない 時だけです。
r'py\\B'
は'python'
、'py3'
、'py2'
にはマッチしますが、'py'
、'py.'
、あるいは'py!'
にはマッチしません。\\B
は\\b
のちょうど反対で、単語の文字は Unicode 英数字 (日本語など英字以外も含む非記号) またはアンダースコアですが、ASCII
フラグで ASCII に限定できます。\d
- ユニコード (str) パターンに対して:
任意の Unicode 10進数 (Unicode 文字カテゴリ [Nd]) とマッチします。これには
[0-9]
とその他の 10 進数文字が含まれます。ASCII
が使用された場合、[0-9]
のみマッチします。ただし、このフラグは正規表現全体に作用しますので、明示的に[0-9]
と指定する方が良い場合があるかもしれません。- 8bit (bytes) パターンに対して:
任意の 10 進数にマッチします; これは
[0-9]
と等価です。
\D
任意の非 Unicode 10 進数文字にマッチします。これは
\\d
の反対です。ASCII
フラグを使用すると[^0-9]
と等価になります。ただし、このフラグは正規表現全体に作用しますので、明示的に[^0-9]
と指定する方が良い場合があるかもしれません。\s
- ユニコード (str) パターンに対して:
任意の空白文字とマッチします。これには
[ \t\n\r\f\v]
およびノーブレークスペースなど、多くの言語におけるタイポグラフィ規則で定義された文字が含まれます。ASCII
フラグを使用すると、[ \\t\\n\\r\\f\\v]
のみにマッチします。ただし、このフラグは正規表現全体に作用しますので、明示的に[ \t\n\r\f\v]
と指定する方が良い場合があるかもしれません。- 8bit (bytes) パターンに対して:
ASCII 文字セットにおける空白文字とマッチします。これは
[ \t\n\r\f\v]
と等価です。
\S
任意の非空白文字にマッチします。これは
\s
の反対です。ASCII
フラグを使用すると[^ \t\n\r\f\v]
と等価になります。ただし、このフラグは正規表現全体に作用しますので、明示的に[^ \t\n\r\f\v]
と指定する方が良い場合があるかもしれません。\w
- ユニコード (str) パターンに対して:
任意の Unicode 単語文字にマッチします。これにはあらゆる言語で単語の一部になりうる文字、数字、およびアンダースコアが含まれます。
ASCII
フラグを使用すると[a-zA-Z0-9_]
のみにマッチします。ただし、このフラグは正規表現全体に作用しますので、明示的に[a-zA-Z0-9_]
と指定する方が良い場合があるかもしれません。- 8bit (bytes) パターンに対して:
ASCII 文字セットでの英数字とアンダースコアにマッチします。これは
[a-zA-Z0-9_]
と等価です。
\W
任意の非 Unicode 単語文字にマッチします。これは
\\w
の反対です。ASCII
フラグを使用した場合、[^a-zA-Z0-9_]
と等価になります。ただし、このフラグは正規表現全体に作用しますので、明示的に[^a-zA-Z0-9_]
と指定する方が良い場合があるかもしれません。\Z
文字列の末尾とのみマッチします。
Python 文字列リテラルによってサポートされている標準エスケープのほとんども、正規表現パーサに認識されます:
\a \b \f \n
\r \t \u \U
\v \x \\
(\b
は単語の境界を表し、文字クラス内でのみ後退 (backspace) 文字を指すことに注意してください)
'\u'
および '\U'
エスケープシーケンスは Unicode パターン内でのみ認識されます。バイト列では特殊文字として扱われません。
8 進数エスケープは限られた形式で表します。最初の桁が 0 か、あるいは 3 桁の 8 進数ならば、8 進数エスケープとみなされます。それ以外の場合はグループ参照になります。文字列リテラルに関しては、8 進数エスケープはほとんどの場合 3 桁長になります。
バージョン 3.3 で変更: '\u'
と '\U'
エスケープシーケンスが追加されました。
6.2.2. モジュールの内容¶
このモジュールはいくつかの関数、定数、例外を定義します。この関数の一部はコンパイルした正規表現の完全版メソッドを簡略化したバージョンです。簡単なアプリケーションを除くほとんどで、コンパイルされた形式が用いられるのが普通です。
-
re.
compile
(pattern, flags=0)¶ 正規表現パターンを正規表現オブジェクトにコンパイルします。このオブジェクトは、以下で述べる
match()
とsearch()
メソッドを使って、マッチングに使うことができます。表現の動作は、flags 値を指定することで調整できます。値は以下の変数を、ビットごとの OR (
|
演算子) を使って組み合わせることができます。シーケンス
prog = re.compile(pattern) result = prog.match(string)
は、
result = re.match(pattern, string)
と等価ですが、
re.compile()
を使ってその結果の正規表現オブジェクトを再利用した方が、その表現を一つのプログラムで何回も使う時には効率的です。注釈
最後に
re.match()
、re.search()
、あるいはre.compile()
に渡されたパターンのコンパイルされたものがキャッシュとして残ります。そのため、正規表現を一つだけしか使わないプログラムは正規表現のコンパイルを気にする必要はありません。
-
re.
A
¶ -
re.
ASCII
¶ \w
、\W
、\b
、\B
、\d
、\D
、\s
、および\S
において、ASCII 文字のみでマッチングを行います。これは Unicode パターンでのみ意味があり、バイト列パターンでは無視されます。後方互換性のため、
re.U
フラグ (およびそれと同義のre.UNICODE
と埋め込みで使用する(?u)
) はまだ存在していますが、文字列のマッチのデフォルトが Unicode になった Python 3 では冗長です (そして Unicode マッチングではバイト列は扱えません)。
-
re.
DEBUG
¶ コンパイルした表現に関するデバッグ情報を出力します。
-
re.
I
¶ -
re.
IGNORECASE
¶ 英大文字・小文字を区別せずにマッチングを行います。
[A-Z]
のような表現は小文字ともマッチします。これは現在のロケールの影響を受けず、Unicode 文字に対しても動作します。
-
re.
L
¶ -
re.
LOCALE
¶ \w
、\W
、\b
、\B
、\s
、および\S
において、ロケールに従ったマッチングを行います。ロケールのメカニズムは非常に信頼性に欠けるため、このフラグの使用は推奨されません。これはいずれにしろ、一度に一つの “文化” を扱うだけです。通常は Python 3 Unicode (文字列) パターンのデフォルトである、Unicode マッチングを使用してください。
-
re.
M
¶ -
re.
MULTILINE
¶ 指定されると、パターン文字
'^'
は、文字列の先頭および各行の先頭 (各改行の直後) とマッチします; そしてパターン文字'$'
は文字列の末尾および各行の末尾 (改行の直前) とマッチします。デフォルトでは、'^'
は、文字列の先頭とだけマッチし、'$'
は、文字列の末尾および文字列の末尾の改行の直前 (もしあれば) とマッチします。
-
re.
X
¶ -
re.
VERBOSE
¶ このフラグによって、より見やすく正規表現を書くことができます。パターン内の空白は、文字クラス内にあるかエスケープされていないバックスラッシュが前にある時以外は無視されます。また、行に、文字クラス内にもなく、エスケープされていないバックスラッシュが前にもない
'#'
がある時は、そのような'#'
の左端からその行の末尾までが無視されます。つまり、数字にマッチする下記の二つの正規表現オブジェクトは、機能的に等価です:
a = re.compile(r"""\d + # the integral part \. # the decimal point \d * # some fractional digits""", re.X) b = re.compile(r"\d+\.\d*")
-
re.
search
(pattern, string, flags=0)¶ string 走査して、正規表現 pattern がマッチを発生する位置を探し、対応する マッチオブジェクト を返します。文字列内のどこにもマッチしない場合は
None
を返します; これは、文字列内のある位置で長さ 0 でマッチした場合と異なることに注意して下さい。
-
re.
match
(pattern, string, flags=0)¶ もし string の先頭で 0 個以上の文字が正規表現 pattern とマッチすれば、対応する マッチオブジェクト インスタンスを返します。文字列がパターンとマッチしなければ、
None
を返します; これは長さゼロのマッチとは異なることに注意して下さい。MULTILINE
モードであっても、re.match()
は文字列の先頭のみにマッチし、各行の先頭にはマッチしないことに注意してください。もし string のマッチする位置を見つけたい場合は、
search()
を使って下さい (search() vs. match() も参照してください)。
-
re.
split
(pattern, string, maxsplit=0, flags=0)¶ string を、pattern があるたびに分割します。キャプチャグループの丸括弧が pattern で使われていれば、パターン内のすべてのグループのテキストも結果のリストの一部として返されます。maxsplit がゼロでなければ、最大 maxsplit 個の分割が発生し、残りはリストの最終要素として返されます。:
>>> re.split('\W+', 'Words, words, words.') ['Words', 'words', 'words', ''] >>> re.split('(\W+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] >>> re.split('\W+', 'Words, words, words.', 1) ['Words', 'words, words.'] >>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE) ['0', '3', '9']
もし、キャプチャするグループが分割パターンに含まれ、それが文字列の先頭にあるならば、分割結果は、空文字列から始まります。文字列最後においても同様です:
>>> re.split('(\W+)', '...words, words...') ['', '...', 'words', ', ', 'words', '...', '']
その場合、常に、分割要素が、分割結果のリストの相対的なインデックスに現れます。
split は空のパターンマッチでは、文字列を分割しないことに注意して下さい。例えば:
>>> re.split('x*', 'foo') ['foo'] >>> re.split("(?m)^$", "foo\n\nbar\n") ['foo\n\nbar\n']
バージョン 3.1 で変更: オプションの flags 引数が追加されました。
-
re.
findall
(pattern, string, flags=0)¶ pattern の string へのマッチのうち、重複しないすべてのマッチを文字列のリストとして返します。string は左から右へと走査され、マッチは見つかった順番で返されます。パターン中に何らかのグループがある場合、グループのリストを返します。グループが複数定義されていた場合、タプルのリストになります。他のマッチの開始部分に接触しないかぎり、空のマッチも結果に含められます。
-
re.
finditer
(pattern, string, flags=0)¶ string 内の RE pattern の重複しないマッチの マッチオブジェクト を yield する イテレータ を返します。string は左から右へと走査され、マッチは見つかった順番で返されます。他のマッチの開始部分に接触しないかぎり、空のマッチも結果に含められます。
-
re.
sub
(pattern, repl, string, count=0, flags=0)¶ string 内で、重複しない pattern のマッチを repl で置き換えた文字列を返します。重複している場合はその一番左のマッチのみ置き換えられます。パターンが見つからなければ string を変更せずに返します。repl は文字列でも関数でも構いません; 文字列の場合、バックスラッシュエスケープは処理されます。すなわち、
\n
は単一の改行文字に変換され、\r
は、キャリッジリターンに変換されます。\j
のような未知のエスケープはそのままにされます。\6
のような後方参照は、パターンのグループ 6 とマッチした部分文字列で置換されます。例えば:>>> re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(\s*\):', ... r'static PyObject*\npy_\1(void)\n{', ... 'def myfunc():') 'static PyObject*\npy_myfunc(void)\n{'
もし repl が関数であれば、重複しない pattern が発生するたびにその関数が呼ばれます。この関数は一つのマッチオブジェクト引数を取り、置換文字列を返します。例えば:
>>> def dashrepl(matchobj): ... if matchobj.group(0) == '-': return ' ' ... else: return '-' >>> re.sub('-{1,2}', dashrepl, 'pro----gram-files') 'pro--gram files' >>> re.sub(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE) 'Baked Beans & Spam'
パターンは、文字列でも RE オブジェクトでも構いません。
省略可能な引数 count は、置換されるパターンの出現回数の最大値です; count は非負の整数でなければなりません。省略されるかゼロであれば、出現したものがすべて置換されます。パターンのマッチが空であれば、以前のマッチと隣合わせでない時だけ置換されますので、
sub('x*', '-', 'abc')
は'-a-b-c-'
を返します。文字列タイプ repl 引数では、エスケープ文字と上述の方向参照に加えて、
\g<name>
は(?P<name>...)
構文で定義されたグループ名name
にマッチした部分文字列を使用します。\g<number>
は対応するグループ番号に使用します; 従って\g<2>
は\2
と等価ですが、\g<2>0
のような曖昧な置き換え文字列とは区別されます。\20
はグループ 20を 参照し、リテラル文字'0'
が続くグループ 2 とはみなされません。後方参照\g<0>
は RE にマッチした部分文字列全体に置き換えられます。バージョン 3.1 で変更: オプションの flags 引数が追加されました。
-
re.
subn
(pattern, repl, string, count=0, flags=0)¶ sub()
と同じ操作を行いますが、タプル(new_string、 number_of_subs_made)
を返します。バージョン 3.1 で変更: オプションの flags 引数が追加されました。
-
re.
escape
(string)¶ パターン内の、ASCII 文字、数字、およびアンダースコアを除くすべての文字をエスケープします。これは、もしその中に正規表現のメタ文字を持つかもしれない任意のリテラル文字列とマッチしたい時に役立ちます。
バージョン 3.3 で変更:
'_'
文字がエスケープされなくなりました。
-
re.
purge
()¶ 正規表現キャッシュをクリアします。
-
exception
re.
error
¶ ここでの関数のいずれかに渡された文字列が正しい正規表現ではない時 (例: その括弧が対になっていない)、あるいはコンパイルやマッチング中になんらかのエラーが発生した時に発生する例外です。文字列がパターンとマッチしない場合はエラーにはなりません。
6.2.3. 正規表現オブジェクト¶
コンパイル済み正規表現オブジェクトは以下のメソッドと属性をサポートします:
-
regex.
search
(string[, pos[, endpos]])¶ string 走査して、この正規表現がマッチを発生する位置を探し、対応する マッチオブジェクト を返します。文字列内のどこにもマッチしない場合は
None
を返します; これは、文字列内のある位置で長さ 0 でマッチした場合と異なることに注意して下さい。省略可能な、2 つ目の引数 pos は、文字列のどこから探し始めるかを指定するインデックスで、デフォルトでは 0 です。これは、文字列をスライスしてから検索するのと、完全には等価ではありません。パターン文字
'^'
は本当の文字列の先頭と、改行の直後にマッチしますが、検索を開始するインデックスがマッチするとは限りません。省略可能な引数 endpos は文字列の検索範囲を制限します。これは文字列の長さが endpos 文字だった場合と同じとみなし、pos から
endpos - 1
の範囲の文字に対してマッチを探します。endpos が pos よりも小さい場合は、マッチは見つかりません。それ以外の場合は、rx がコンパイルされた正規表現として、rx.search(string, 0, 50)
はrx.search(string[:50], 0)
と等価です。>>> pattern = re.compile("d") >>> pattern.search("dog") # Match at index 0 <_sre.SRE_Match object at ...> >>> pattern.search("dog", 1) # No match; search doesn't include the "d"
-
regex.
match
(string[, pos[, endpos]])¶ もし string の 先頭の 0 個以上の文字がこの正規表現とマッチすれば、対応する マッチオブジェクト を返します。もし文字列がパタンーとマッチしなければ、
None
を返します。これは長さゼロのマッチとは異なることに注意して下さい。省略可能な引数 pos と endpos 引数は、
search()
メソッドと同じ意味を持ちます。>>> pattern = re.compile("o") >>> pattern.match("dog") # No match as "o" is not at the start of "dog". >>> pattern.match("dog", 1) # Match as "o" is the 2nd character of "dog". <_sre.SRE_Match object at ...>
string のどこにでもマッチさせたければ、
search()
を使って下さい (search() vs. match() も参照してください)。
-
regex.
findall
(string[, pos[, endpos]])¶ findall()
関数と同様で、コンパイルしたパターンを使います。ただし、match()
と同じように、省略可能な pos、endpos 引数で検索範囲を指定することができます。
-
regex.
finditer
(string[, pos[, endpos]])¶ finditer()
関数と同様で、コンパイルしたパターンを使います。ただし、match()
と同じように、省略可能な pos、endpos 引数で検索範囲を指定することができます。
-
regex.
flags
¶ 正規表現のマッチングフラグです。これは
compile()
で指定されたフラグ、パターン内の(?...)
インラインフラグ、およびパターンが Unicode 文字列だった時のUNICODE
のような暗黙のフラグとの組み合わせになりなす。
-
regex.
groups
¶ パターン内のキャプチャグループの数です。
-
regex.
groupindex
¶ (?P<id>)
で定義された任意の記号グループ名の、グループ番号への辞書マッピングです。もし記号グループがパターン内で何も使われていなければ、辞書は空です。
-
regex.
pattern
¶ RE オブジェクトがコンパイルされたパターン文字列です。
6.2.4. Match オブジェクト¶
マッチオブジェクトは常にブール値 True
を持ちます。 match()
と search()
はマッチしなかった場合に None
を返すので、単純な if
ステートメントによってマッチしたかどうかをテストできます:
match = re.search(pattern, string)
if match:
process(match)
マッチオブジェクトは以下のメソッドと属性をサポートしています:
-
match.
expand
(template)¶ テンプレート文字列 template に対し、
sub()
メソッドがするようなバックスラッシュ置換をして得られる文字列を返します。\n
のようなエスケープは適切な文字に変換され、数値の後方参照 (\1
,\2
) と名前付きの後方参照 (\g<1>
,\g<name>
) は、対応するグループの内容で置き換えられます。
-
match.
group
([group1, ...])¶ マッチした 1 個以上のサブグループを返します。引数が 1 個の場合、その結果は 1 個の文字列です。複数の引数があれば、その結果は引数ごとに項目を持つタプルになります。引数がなければ group1 はデフォルトでゼロです (マッチしたものすべてが返されます)。もし groupN 引数がゼロであれば、対応する戻り値は、マッチする文字列全体になります。もしそれが範囲 [1..99] 内であれば、それは対応する丸括弧つきグループとマッチする文字列です。もしグループ番号が負であるか、あるいはパターンで定義されたグループの数より大きければ、
IndexError
例外を送出します。グループがマッチしなかったパターンの一部に含まれていれば、対応する結果はNone
です。グループが、複数回マッチしたパターンの一部に含まれていれば、最後のマッチが返されます。>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist") >>> m.group(0) # The entire match 'Isaac Newton' >>> m.group(1) # The first parenthesized subgroup. 'Isaac' >>> m.group(2) # The second parenthesized subgroup. 'Newton' >>> m.group(1, 2) # Multiple arguments give us a tuple. ('Isaac', 'Newton')
正規表現が
(?P<name>...)
構文を使用している場合、groupN 引数は、グループ名を識別する文字列であっても構いません。文字列引数がパターン内でグループ名として使用されていない場合は、IndexError
例外が送出されます。適度に複雑な例題:
>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds") >>> m.group('first_name') 'Malcolm' >>> m.group('last_name') 'Reynolds'
名前の付けられたグループは、そのインデックスによっても参照できます:
>>> m.group(1) 'Malcolm' >>> m.group(2) 'Reynolds'
グループが複数回マッチする場合、最後のマッチだけが利用できます:
>>> m = re.match(r"(..)+", "a1b2c3") # Matches 3 times. >>> m.group(1) # Returns only the last match. 'c3'
-
match.
groups
(default=None)¶ パターンにマッチしたすべてのサブグループを含む、パターン内で指定されたグループ数分の要素を持つタプルを返します。引数 default は、マッチに加わらなかったグループ用に使われ、デフォルトでは
None
です。例えば:
>>> m = re.match(r"(\d+)\.(\d+)", "24.1632") >>> m.groups() ('24', '1632')
整数部にのみ着目し、あとの部分をオプションとする場合、マッチの中に現れないグループがあるかも知れません。それらのグループは、引数 default が与えられていない場合、デフォルトの
None
になります:>>> m = re.match(r"(\d+)\.?(\d+)?", "24") >>> m.groups() # Second group defaults to None. ('24', None) >>> m.groups('0') # Now, the second group defaults to '0'. ('24', '0')
-
match.
groupdict
(default=None)¶ マッチしたすべての 名前つき サブグループを含む、サブグループ名でキー付けされた辞書を返します。引数 default はマッチに加わらなかったグループに使われ、デフォルトは
None
です。例えば:>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds") >>> m.groupdict() {'first_name': 'Malcolm', 'last_name': 'Reynolds'}
-
match.
start
([group])¶ -
match.
end
([group])¶ group とマッチした部分文字列の先頭と末尾のインデックスを返します。group は、デフォルトでは (マッチした部分文字列全体を意味する)ゼロです。group が存在してもマッチに寄与しなかった場合は、
-1
を返します。マッチオブジェクト m および、マッチに寄与しなかったグループ g があって、グループ g とマッチした部分文字列 (m.group(g)
と同じ意味ですが) は:m.string[m.start(g):m.end(g)]
です。もし group が空文字列とマッチすれば、
m.start(group)
がm.end(group)
と等しくなることに注意して下さい。例えば、m = re.search('b(c?)', 'cba')
とすると、m.start(0)
は 1 で、m.end(0)
は 2 であり、m.start(1)
とm.end(1)
はともに 2 であり、m.start(2)
はIndexError
例外を送出します。例として、電子メールのアドレスから remove_this を取り除く場合を示します:
>>> email = "tony@tiremove_thisger.net" >>> m = re.search("remove_this", email) >>> email[:m.start()] + email[m.end():] 'tony@tiger.net'
-
match.
span
([group])¶ マッチ m について、大きさ2のタプル
(m.start(group), m.end(group))
を返します。もし group がマッチに寄与しなければ、(-1, -1)
になります。また group はデフォルトでゼロです。
-
match.
pos
¶ 正規表現オブジェクト の
search()
かmatch()
に渡された pos の値です。これは RE エンジンがマッチを探し始める位置の文字列のインデックスです。
-
match.
endpos
¶ 正規表現オブジェクト の
search()
かmatch()
に渡された endpos の値です。これは RE エンジンがそれ以上は進まない位置の文字列のインデックスです。
-
match.
lastindex
¶ 最後にマッチしたキャプチャグループの整数インデックスです。どのグループもマッチしなければ
None
です。例えば、(a)b
、((a)(b))
、あるいは((ab))
といった表現が'ab'
に適用された場合、lastindex == 1
となり、同じ文字列に(a)(b)
が適用された場合にはlastindex == 2
となります。
-
match.
lastgroup
¶ 最後にマッチしたキャプチャグループの名前です。もしグループに名前がないか、あるいはどのグループもマッチしなければ
None
です。
6.2.5. 正規表現の例¶
6.2.5.1. ペアの確認¶
この例では、マッチオブジェクトの表示を少し美しくするために、以下の補助関数を使用します:
def displaymatch(match):
if match is None:
return None
return '<Match: %r, groups=%r>' % (match.group(), match.groups())
あなたはポーカープログラムを書いているとします。プレイヤーの持ち札はそれぞれの文字が 1 枚のカードを意味する 5 文字の文字列で表現されます。”a” はエース、”k” はキング、”q” はクイーン、”j” はジャック “0” は10、そして “2” から “9” はそれぞれの数字のカードを表します。
与えられた文字列が持ち札として有効かどうかの確認は、下記のようにして行えます:
>>> valid = re.compile(r"^[a2-9tjqk]{5}$")
>>> displaymatch(valid.match("akt5q")) # Valid.
"<Match: 'akt5q', groups=()>"
>>> displaymatch(valid.match("akt5e")) # Invalid.
>>> displaymatch(valid.match("akt")) # Invalid.
>>> displaymatch(valid.match("727ak")) # Valid.
"<Match: '727ak', groups=()>"
最後の持ち札 "727ak"
は、ペアを含んでいます。言い換えると同じ値のカードが 2 枚あります。これを正規表現にマッチさせるには、後方参照を使います:
>>> pair = re.compile(r".*(.).*\1")
>>> displaymatch(pair.match("717ak")) # Pair of 7s.
"<Match: '717', groups=('7',)>"
>>> displaymatch(pair.match("718ak")) # No pairs.
>>> displaymatch(pair.match("354aa")) # Pair of aces.
"<Match: '354aa', groups=('a',)>"
どのカードのペアになっているかを調べるには、以下のようにマッチオブジェクトの group()
メソッドを使います:
>>> pair.match("717ak").group(1)
'7'
# Error because re.match() returns None, which doesn't have a group() method:
>>> pair.match("718ak").group(1)
Traceback (most recent call last):
File "<pyshell#23>", line 1, in <module>
re.match(r".*(.).*\1", "718ak").group(1)
AttributeError: 'NoneType' object has no attribute 'group'
>>> pair.match("354aa").group(1)
'a'
6.2.5.2. scanf() をシミュレートする¶
Python には現在のところ、scanf()
に相当するものがありません。正規表現は、scanf()
のフォーマット文字列よりも一般により強力であり、また冗長でもあります。以下の表に scanf()
のフォーマットトークンにほぼ等価に対応する正規表現を示します。
|
正規表現 |
---|---|
%c |
. |
%5c |
.{5} |
%d |
[-+]?\d+ |
%e , %E , %f , %g |
[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)? |
%i |
[-+]?(0[xX][\dA-Fa-f]+|0[0-7]*|\d+) |
%o |
[-+]?[0-7]+ |
%s |
\S+ |
%u |
\d+ |
%x , %X |
[-+]?(0[xX])?[\dA-Fa-f]+ |
以下のような文字列からファイル名と数値を抽出するには
/usr/sbin/sendmail - 0 errors, 4 warnings
このように scanf()
フォーマットを使うでしょう
%s - %d errors, %d warnings
それと等価な正規表現は以下のとおりです
(\S+) - (\d+) errors, (\d+) warnings
6.2.5.3. search() vs. match()¶
Python は正規表現ベースの 2 個の基本的な関数、文字列の先頭でのみのマッチを確認する re.match()
および、文字列内の位置にかかわらずマッチを確認する re.search()
(Perl でのデフォルトの挙動) を提供しています。
例えば:
>>> re.match("c", "abcdef") # No match
>>> re.search("c", "abcdef") # Match
<_sre.SRE_Match object at ...>
'^'
で始まる正規表現は、search()
において、マッチを文字列の先頭からに制限するために使用します:
>>> re.match("c", "abcdef") # No match
>>> re.search("^c", "abcdef") # No match
>>> re.search("^a", "abcdef") # Match
<_sre.SRE_Match object at ...>
ただし、MULTILINE
モードの match()
では文字列の先頭にのみマッチするのに対し、正規表現に '^'
を使った search()
では各行の先頭にもマッチします。
>>> re.match('X', 'A\nB\nX', re.MULTILINE) # No match
>>> re.search('^X', 'A\nB\nX', re.MULTILINE) # Match
<_sre.SRE_Match object at ...>
6.2.5.4. 電話帳の作成¶
split()
は文字列を与えられたパターンで分割し、リストにして返します。下記の、電話帳作成の例のように、このメソッドはテキストデータを読みやすくしたり、Python で編集したりしやすくする際に、非常に役に立ちます。
最初に、入力を示します。通常、これはファイルからの入力になるでしょう。ここでは、3 重引用符の書式とします:
>>> text = """Ross McFluff: 834.345.1254 155 Elm Street
...
... Ronald Heathmore: 892.345.3428 436 Finley Avenue
... Frank Burger: 925.541.7625 662 South Dogwood Way
...
...
... Heather Albrecht: 548.326.4584 919 Park Place"""
個々の記録は、1 つ以上の改行で区切られています。まずは、文字列から空行を除き、記録ごとのリストに変換します:
>>> entries = re.split("\n+", text)
>>> entries
['Ross McFluff: 834.345.1254 155 Elm Street',
'Ronald Heathmore: 892.345.3428 436 Finley Avenue',
'Frank Burger: 925.541.7625 662 South Dogwood Way',
'Heather Albrecht: 548.326.4584 919 Park Place']
そして、各記録を、名、姓、電話番号、そして、住所に分割してリストにします。分割のためのパターンに使っている空白文字が、住所には含まれるため、split()
の maxsplit
引数を使います:
>>> [re.split(":? ", entry, 3) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155 Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436 Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662 South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919 Park Place']]
パターン、:?
は姓に続くコロンにマッチします。そのため、コロンは分割結果のリストには現れません。maxsplit
を 4
にすれば、ハウスナンバーと、ストリート名を分割することができます:
>>> [re.split(":? ", entry, 4) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155', 'Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436', 'Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662', 'South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919', 'Park Place']]
6.2.5.5. テキストの秘匿¶
sub()
はパターンにマッチした部分を文字列や関数の戻り値で置き換えます。この例では、”秘匿” する文字列に、関数と共に sub()
を適用する例を示します。言い換えると、最初と最後の文字を除く、単語中の文字の位置をランダム化します:
>>> def repl(m):
... inner_word = list(m.group(2))
... random.shuffle(inner_word)
... return m.group(1) + "".join(inner_word) + m.group(3)
>>> text = "Professor Abdolmalek, please report your absences promptly."
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Poefsrosr Aealmlobdk, pslaee reorpt your abnseces plmrptoy.'
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Pofsroser Aodlambelk, plasee reoprt yuor asnebces potlmrpy.'
6.2.5.6. すべての形容動詞を見つける¶
findall()
はパターンにマッチする すべてに マッチします。search()
がそうであるように、最初のものだけに、ではありません。例えば、なにかの文章のすべての副詞を見つけたいとき、下記のように findall()
を使います:
>>> text = "He was carefully disguised but captured quickly by police."
>>> re.findall(r"\w+ly", text)
['carefully', 'quickly']
6.2.5.7. すべての形容動詞と、その位置を見つける¶
もし、パターンにマッチするものについて、マッチしたテキスト以上の情報を得たいと考えた時、文字列ではなく マッチオブジェクト を返す finditer()
が便利です。以下に例を示すように、なにかの文章のすべての副詞と、その位置を 調べたい時、以下のように finditer()
を使います:
>>> text = "He was carefully disguised but captured quickly by police."
>>> for m in re.finditer(r"\w+ly", text):
... print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))
07-16: carefully
40-47: quickly
6.2.5.8. Raw 文字列記法¶
Raw 文字列記法 (r"text"
) により、バックスラッシュ ('\'
) を個々にバックスラッシュでエスケープすることなしに、正規表現を正常な状態に保つことができます。例えば、以下の 2 つのコードは機能的に等価です:
>>> re.match(r"\W(.)\1\W", " ff ")
<_sre.SRE_Match object at ...>
>>> re.match("\\W(.)\\1\\W", " ff ")
<_sre.SRE_Match object at ...>
文字通りのバックスラッシュにマッチさせたいなら、正規表現中ではエスケープする必要があります。Raw 文字列記法では、r"\\"
になります。Raw 文字列記法を用いない場合、"\\\\"
としなくてはなりません。以下のコードは機能的に等価です:
>>> re.match(r"\\", r"\\")
<_sre.SRE_Match object at ...>
>>> re.match("\\\\", r"\\")
<_sre.SRE_Match object at ...>
6.2.5.9. トークナイザを書く¶
トークナイザやスキャナ は文字列を解析し、文字のグループにカテゴリ分けします。これはコンパイラやインタプリタを作成する最初の一歩として役立ちます。
テキストのカテゴリは正規表現で指定されます。技術的には、それらを一つのマスター正規表現に結合し、連続したマッチをループさせます:
import collections
import re
Token = collections.namedtuple('Token', ['typ', 'value', 'line', 'column'])
def tokenize(s):
keywords = {'IF', 'THEN', 'ENDIF', 'FOR', 'NEXT', 'GOSUB', 'RETURN'}
token_specification = [
('NUMBER', r'\d+(\.\d*)?'), # Integer or decimal number
('ASSIGN', r':='), # Assignment operator
('END', r';'), # Statement terminator
('ID', r'[A-Za-z]+'), # Identifiers
('OP', r'[+*\/\-]'), # Arithmetic operators
('NEWLINE', r'\n'), # Line endings
('SKIP', r'[ \t]'), # Skip over spaces and tabs
]
tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification)
get_token = re.compile(tok_regex).match
line = 1
pos = line_start = 0
mo = get_token(s)
while mo is not None:
typ = mo.lastgroup
if typ == 'NEWLINE':
line_start = pos
line += 1
elif typ != 'SKIP':
val = mo.group(typ)
if typ == 'ID' and val in keywords:
typ = val
yield Token(typ, val, line, mo.start()-line_start)
pos = mo.end()
mo = get_token(s, pos)
if pos != len(s):
raise RuntimeError('Unexpected character %r on line %d' %(s[pos], line))
statements = '''
IF quantity THEN
total := total + price * quantity;
tax := price * 0.05;
ENDIF;
'''
for token in tokenize(statements):
print(token)
トークナイザは以下の出力を作成します:
Token(typ='IF', value='IF', line=2, column=5)
Token(typ='ID', value='quantity', line=2, column=8)
Token(typ='THEN', value='THEN', line=2, column=17)
Token(typ='ID', value='total', line=3, column=9)
Token(typ='ASSIGN', value=':=', line=3, column=15)
Token(typ='ID', value='total', line=3, column=18)
Token(typ='OP', value='+', line=3, column=24)
Token(typ='ID', value='price', line=3, column=26)
Token(typ='OP', value='*', line=3, column=32)
Token(typ='ID', value='quantity', line=3, column=34)
Token(typ='END', value=';', line=3, column=42)
Token(typ='ID', value='tax', line=4, column=9)
Token(typ='ASSIGN', value=':=', line=4, column=13)
Token(typ='ID', value='price', line=4, column=16)
Token(typ='OP', value='*', line=4, column=22)
Token(typ='NUMBER', value='0.05', line=4, column=24)
Token(typ='END', value=';', line=4, column=28)
Token(typ='ENDIF', value='ENDIF', line=5, column=5)
Token(typ='END', value=';', line=5, column=10)