正規表現HOWTO
- 著者
- 午前 Kuchling < [email protected] >>
序章
正規表現(RE、またはregexes、またはregexパターンと呼ばれる)は、本質的にPython内に埋め込まれ、 re モジュールを通じて利用できるようになる非常に特殊なプログラミング言語です。 この小さな言語を使用して、照合する可能性のある文字列のセットのルールを指定します。 このセットには、英語の文章、電子メールアドレス、TeXコマンド、またはその他の好きなものが含まれている可能性があります。 次に、「この文字列はパターンと一致しますか?」または「この文字列のどこかにパターンと一致しますか?」などの質問をすることができます。 REを使用して、文字列を変更したり、さまざまな方法で文字列を分割したりすることもできます。
正規表現パターンは一連のバイトコードにコンパイルされ、Cで記述されたマッチングエンジンによって実行されます。 高度な使用では、エンジンが特定のREを実行する方法に注意を払い、より高速に実行されるバイトコードを生成するために特定の方法でREを書き込む必要がある場合があります。 最適化については、マッチングエンジンの内部を十分に理解している必要があるため、このドキュメントでは取り上げていません。
正規表現言語は比較的小さく制限されているため、正規表現を使用してすべての可能な文字列処理タスクを実行できるわけではありません。 正規表現でできるタスクもありますが、式は非常に複雑であることがわかります。 このような場合は、Pythonコードを記述して処理を行う方がよい場合があります。 Pythonコードは複雑な正規表現よりも遅くなりますが、おそらくより理解しやすいでしょう。
シンプルなパターン
まず、可能な限り単純な正規表現について学習します。 正規表現は文字列の操作に使用されるため、最も一般的なタスクである文字の照合から始めます。
正規表現(決定性および非決定性有限オートマトン)の基礎となるコンピューターサイエンスの詳細な説明については、コンパイラーの作成に関するほとんどすべての教科書を参照できます。
一致する文字
ほとんどの文字と文字は、単に自分自身と一致します。 たとえば、正規表現test
は、文字列test
と完全に一致します。 (このREがTest
またはTEST
にも一致するように、大文字と小文字を区別しないモードを有効にすることができます。これについては後で詳しく説明します。)
この規則には例外があります。 一部の文字は特別なメタ文字であり、それら自体と一致しません。 代わりに、それらは、いくつかの異常なものを一致させる必要があることを通知します。または、それらを繰り返したり、意味を変更したりすることで、REの他の部分に影響を与えます。 このドキュメントの多くは、さまざまなメタ文字とその機能について説明しています。
メタ文字の完全なリストは次のとおりです。 それらの意味については、このHOWTOの残りの部分で説明します。
. ^ $ * + ? { } [ ] \ | ( )
最初に確認するメタ文字は、[
と]
です。 これらは、照合する文字のセットである文字クラスを指定するために使用されます。 文字を個別にリストすることも、2文字を指定して'-'
で区切ることで文字の範囲を示すこともできます。 たとえば、[abc]
は、a
、b
、またはc
のいずれかの文字と一致します。 これは、[a-c]
と同じで、範囲を使用して同じ文字セットを表します。 小文字のみを照合する場合、REは[a-z]
になります。
メタ文字はクラス内ではアクティブではありません。 たとえば、[akm$]
は、'a'
、'k'
、'm'
、または'$'
のいずれかの文字と一致します。 '$'
は通常メタ文字ですが、文字クラス内ではその特殊な性質が取り除かれています。
セットを補完することで、クラスにリストされていない文字を一致させることができます。 これは、クラスの最初の文字として'^'
を含めることで示されます。 たとえば、[^5]
は、'5'
以外のすべての文字と一致します。 キャレットが文字クラスの他の場所にある場合、特別な意味はありません。 例:[5^]
は、'5'
または'^'
のいずれかに一致します。
おそらく最も重要なメタ文字はバックスラッシュ\
です。 Python文字列リテラルと同様に、バックスラッシュの後にさまざまな文字を続けて、さまざまな特別なシーケンスを示すことができます。 また、すべてのメタ文字をエスケープするためにも使用されるため、パターンでそれらを一致させることができます。 たとえば、[
または\
と一致させる必要がある場合は、それらの前に円記号を付けて、\[
または\\
という特別な意味を削除できます。
'\'
で始まる特別なシーケンスの一部は、数字のセット、文字のセット、空白以外の文字のセットなど、多くの場合に役立つ定義済みの文字のセットを表します。
例を見てみましょう。\w
は任意の英数字に一致します。 正規表現パターンがバイトで表される場合、これはクラス[a-zA-Z0-9_]
と同等です。 正規表現パターンが文字列の場合、\w
は、 unicodedata モジュールによって提供されるUnicodeデータベースで文字としてマークされたすべての文字と一致します。 正規表現のコンパイル時に re.ASCII フラグを指定することにより、文字列パターンで\w
のより制限された定義を使用できます。
次の特別なシーケンスのリストは完全ではありません。 Unicode文字列パターンのシーケンスと拡張クラス定義の完全なリストについては、標準ライブラリリファレンスの正規表現構文の最後の部分を参照してください。 一般に、Unicodeバージョンは、Unicodeデータベースの適切なカテゴリにあるすべての文字と一致します。
\d
- 任意の10進数に一致します。 これは、クラス
[0-9]
と同等です。 \D
- 数字以外の文字に一致します。 これは、クラス
[^0-9]
と同等です。 \s
- 任意の空白文字に一致します。 これは、クラス
[ \t\n\r\f\v]
と同等です。 \S
- 空白以外の文字に一致します。 これは、クラス
[^ \t\n\r\f\v]
と同等です。 \w
- 任意の英数字に一致します。 これは、クラス
[a-zA-Z0-9_]
と同等です。 \W
- 英数字以外の文字に一致します。 これは、クラス
[^a-zA-Z0-9_]
と同等です。
これらのシーケンスは、文字クラス内に含めることができます。 たとえば、[\s,.]
は、任意の空白文字、または','
または'.'
に一致する文字クラスです。
このセクションの最後のメタ文字は.
です。 改行文字以外のものと一致し、改行でも一致する代替モード( re.DOTALL )があります。 .
は、「任意の文字」に一致させたい場合によく使用されます。
繰り返すこと
さまざまな文字セットに一致できることは、正規表現で最初にできることですが、文字列で使用できるメソッドではまだ不可能です。 ただし、それが正規表現の唯一の追加機能である場合、それらはそれほど進歩しません。 もう1つの機能は、REの一部を特定の回数繰り返す必要があることを指定できることです。
これから説明することを繰り返すための最初のメタ文字は、*
です。 *
がリテラル文字'*'
と一致しません。 代わりに、前の文字を1回だけではなく、0回以上一致させることができることを指定します。
たとえば、ca*t
は'ct'
(0 'a'
文字)、'cat'
(1 'a'
)、'caaat'
と一致します。 (3 'a'
文字)など。
*
などの繰り返しは貪欲です。 REを繰り返す場合、マッチングエンジンは可能な限りそれを繰り返そうとします。 パターンの後半部分が一致しない場合、一致するエンジンがバックアップし、より少ない繰り返しで再試行します。
ステップバイステップの例は、これをより明白にします。 式a[bcd]*b
を考えてみましょう。 これは、文字'a'
、クラス[bcd]
の0個以上の文字と一致し、最後に'b'
で終わります。 ここで、このREを文字列'abcbd'
と照合することを想像してください。
ステップ | 一致 | 説明 |
---|---|---|
1 | a
|
REのa が一致します。
|
2 | abcbd
|
エンジンは[bcd]* と一致し、可能な限り文字列の最後まで進みます。
|
3 | 失敗 | エンジンはb と一致しようとしますが、現在の位置が文字列の最後にあるため、失敗します。
|
4 | abcb
|
[bcd]* が一致する文字が1つ少なくなるようにバックアップします。
|
5 | 失敗 | b を再試行しますが、現在の位置は最後の文字である'd' です。
|
6 | abc
|
[bcd]* がbc とのみ一致するように、再度バックアップします。
|
6 | abcb
|
b を再試行してください。 今回は現在位置のキャラクターが'b' なので成功します。
|
REの終わりに到達し、'abcb'
と一致しました。 これは、マッチングエンジンが最初に可能な限り実行され、一致するものが見つからない場合は、徐々にバックアップし、残りのREを何度も再試行することを示しています。 [bcd]*
の一致がゼロになるまでバックアップし、その後失敗した場合、エンジンは文字列がREとまったく一致しないと判断します。
もう1つの繰り返しメタ文字は+
で、これは1回以上一致します。 *
と+
の違いに注意してください。 *
はゼロ以上の回数一致するため、繰り返されているものがまったく存在しない可能性がありますが、+
には少なくとも 1回の出現が必要です。 同様の例を使用すると、ca+t
は'cat'
(1 'a'
)、'caaat'
(3 'a'
s)と一致しますが、一致しません。 tは'ct'
と一致します。
さらに2つの繰り返し修飾子があります。 疑問符文字?
は、1回または0回一致します。 何かをオプションとしてマークすることと考えることができます。 たとえば、home-?brew
は'homebrew'
または'home-brew'
のいずれかに一致します。
最も複雑な繰り返し修飾子は{m,n}
です。ここで、 m と n は10進整数です。 この修飾子は、少なくとも m の繰り返し、最大で n が必要であることを意味します。 たとえば、a/{1,3}b
は、'a/b'
、'a//b'
、および'a///b'
と一致します。 スラッシュのない'ab'
や4つの'a////b'
とは一致しません。
m または n のいずれかを省略できます。 その場合、欠落している値には妥当な値が想定されます。 m を省略すると、下限は0として解釈され、 n を省略すると、上限は無限大になります。
還元主義者の読者は、他の3つの修飾子がすべてこの表記法を使用して表現できることに気付くかもしれません。 {0,}
は*
と同じであり、{1,}
は+
と同等であり、{0,1}
は?
と同じです。 。 *
、+
、または?
は、短くて読みやすいという理由だけで、可能な場合は使用することをお勧めします。
正規表現の使用
いくつかの単純な正規表現を見てきましたが、実際にPythonでそれらをどのように使用しますか? re モジュールは、正規表現エンジンへのインターフェイスを提供し、REをオブジェクトにコンパイルして、それらとの照合を実行できるようにします。
正規表現のコンパイル
正規表現はパターンオブジェクトにコンパイルされます。パターンオブジェクトには、パターンの一致の検索や文字列の置換の実行など、さまざまな操作のメソッドがあります。
>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')
re.compile()は、オプションの flags 引数も受け入れます。これは、さまざまな特別な機能や構文のバリエーションを有効にするために使用されます。 利用可能な設定については後で説明しますが、今のところ1つの例で説明します。
>>> p = re.compile('ab*', re.IGNORECASE)
REは文字列として re.compile()に渡されます。 正規表現はコアPython言語の一部ではなく、それらを表現するための特別な構文が作成されていないため、REは文字列として処理されます。 (REをまったく必要としないアプリケーションがあるため、REを含めることで言語仕様を肥大化させる必要はありません。)代わりに、 re モジュールは、Pythonに含まれているC拡張モジュールです。 socket または zlib モジュール。
REを文字列に入れると、Python言語が単純になりますが、次のセクションのトピックである1つの欠点があります。
バックスラッシュペスト
前述のように、正規表現では円記号('\'
)を使用して、特殊形式を示したり、特殊な意味を呼び出さずに特殊文字を使用できるようにします。 これは、Pythonが文字列リテラルで同じ目的で同じ文字を使用することと矛盾します。
LaTeXファイルにある可能性のある文字列\section
に一致するREを作成するとします。 プログラムコードに何を書くかを理解するには、一致させたい文字列から始めます。 次に、バックスラッシュやその他のメタ文字の前にバックスラッシュを付けてエスケープする必要があります。その結果、文字列\\section
が生成されます。 re.compile()に渡す必要のある結果の文字列は、\\section
である必要があります。 ただし、これをPython文字列リテラルとして表現するには、両方の円記号を再度エスケープする必要があります。
キャラクター | ステージ |
---|---|
\section
|
照合するテキスト文字列 |
\\section
|
re.compile()のバックスラッシュをエスケープしました |
"\\\\section"
|
文字列リテラルのエスケープされたバックスラッシュ |
つまり、リテラルのバックスラッシュと一致させるには、正規表現が\\
である必要があり、各バックスラッシュが\\
として表現される必要があるため、RE文字列として'\\\\'
を記述する必要があります。通常のPython文字列リテラル内。 バックスラッシュを繰り返し使用するREでは、これにより多くのバックスラッシュが繰り返され、結果の文字列が理解しにくくなります。
解決策は、正規表現にPythonの生の文字列表記を使用することです。 バックスラッシュは、'r'
で始まる文字列リテラルでは特別な方法で処理されないため、r"\n"
は、'\'
と'n'
を含む2文字の文字列です。 "\n"
は、改行を含む1文字の文字列です。 正規表現は、多くの場合、この生の文字列表記を使用してPythonコードで記述されます。
さらに、正規表現では有効であるがPython文字列リテラルとしては無効な特別なエスケープシーケンスは、 DeprecationWarning になり、最終的に SyntaxError になります。つまり、シーケンスは次のようになります。生の文字列表記またはバックスラッシュのエスケープが使用されていない場合は無効です。
通常の文字列 | 生の文字列 |
---|---|
"ab*"
|
r"ab*"
|
"\\\\section"
|
r"\\section"
|
"\\w+\\s+\\1"
|
r"\w+\s+\1"
|
試合の実行
コンパイルされた正規表現を表すオブジェクトを取得したら、それをどのように処理しますか? パターンオブジェクトには、いくつかのメソッドと属性があります。 ここでは、最も重要なものだけを取り上げます。 完全なリストについては、 re ドキュメントを参照してください。
メソッド/属性 | 目的 |
---|---|
match()
|
文字列の先頭でREが一致するかどうかを判別します。 |
search()
|
文字列をスキャンして、このREが一致する場所を探します。 |
findall()
|
REが一致するすべての部分文字列を検索し、それらをリストとして返します。 |
finditer()
|
REが一致するすべての部分文字列を検索し、それらをイテレータとして返します。 |
match()および search()は、一致するものが見つからない場合、None
を返します。 成功すると、 match object インスタンスが返されます。このインスタンスには、一致に関する情報(開始位置と終了位置、一致したサブストリングなど)が含まれています。
re モジュールをインタラクティブに試すことで、これについて学ぶことができます。 tkinter を利用できる場合は、Pythonディストリビューションに含まれているデモプログラムである:source: `Tools / demo / redemo.py` も参照してください。 REと文字列を入力でき、REが一致するか失敗するかを表示します。 redemo.py
は、複雑なREをデバッグするときに非常に役立ちます。
このHOWTOは、例として標準のPythonインタープリターを使用しています。 まず、Pythonインタープリターを実行し、 re モジュールをインポートして、REをコンパイルします。
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')
これで、RE [a-z]+
に対してさまざまな文字列を照合してみることができます。 +
は「1回以上の繰り返し」を意味するため、空の文字列はまったく一致しないはずです。 この場合、 match()はNone
を返す必要があります。これにより、インタープリターは出力を出力しません。 match()
の結果を明示的に印刷して、これを明確にすることができます。
>>> p.match("")
>>> print(p.match(""))
None
それでは、tempo
などの一致するはずの文字列で試してみましょう。 この場合、 match()は matchオブジェクトを返すため、後で使用するために結果を変数に格納する必要があります。
>>> m = p.match('tempo')
>>> m
<re.Match object; span=(0, 5), match='tempo'>
これで、一致オブジェクトに、一致する文字列に関する情報を照会できます。 一致オブジェクトインスタンスにもいくつかのメソッドと属性があります。 最も重要なものは次のとおりです。
メソッド/属性 | 目的 |
---|---|
group()
|
REと一致する文字列を返します |
start()
|
試合の開始位置を返す |
end()
|
試合の終了位置を返す |
span()
|
一致の(開始、終了)位置を含むタプルを返します |
これらの方法を試してみると、すぐにその意味が明らかになります。
>>> m.group()
'tempo'
>>> m.start(), m.end()
(0, 5)
>>> m.span()
(0, 5)
group()は、REによって一致したサブストリングを返します。 start()および end()は、一致の開始インデックスと終了インデックスを返します。 span()は、開始インデックスと終了インデックスの両方を単一のタプルで返します。 match()メソッドは、文字列の先頭でREが一致するかどうかのみをチェックするため、start()
は常にゼロになります。 ただし、パターンの search()メソッドは文字列をスキャンするため、その場合、一致はゼロから開始されない可能性があります。
>>> print(p.match('::: message'))
None
>>> m = p.search('::: message'); print(m)
<re.Match object; span=(4, 11), match='message'>
>>> m.group()
'message'
>>> m.span()
(4, 11)
実際のプログラムでは、一致オブジェクトを変数に格納し、それがNone
であるかどうかを確認するのが最も一般的なスタイルです。 これは通常次のようになります。
p = re.compile( ... )
m = p.match( 'string goes here' )
if m:
print('Match found: ', m.group())
else:
print('No match')
2つのパターンメソッドは、パターンのすべての一致を返します。 findall()は、一致する文字列のリストを返します。
>>> p = re.compile(r'\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']
この例では、リテラルを生の文字列リテラルにするr
プレフィックスが必要です。これは、正規表現とは対照的に、Pythonで認識されない通常の「調理済み」文字列リテラルのエスケープシーケンスが結果として DeprecationWarning であり、最終的には SyntaxError になります。 バックスラッシュペストを参照してください。
findall()は、結果として返される前に、リスト全体を作成する必要があります。 finditer()メソッドは、一致オブジェクトインスタンスのシーケンスをイテレーターとして返します。
>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
>>> iterator
<callable_iterator object at 0x...>
>>> for match in iterator:
... print(match.span())
...
(0, 2)
(22, 24)
(29, 31)
モジュールレベルの機能
パターンオブジェクトを作成してそのメソッドを呼び出す必要はありません。 re モジュールは、 match()、 search()、 findall()、 subと呼ばれるトップレベルの関数も提供します。 ()など。 これらの関数は、最初の引数としてRE文字列が追加された対応するパターンメソッドと同じ引数を取り、None
または match object インスタンスのいずれかを返します。
>>> print(re.match(r'From\s+', 'Fromage amk'))
None
>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')
<re.Match object; span=(0, 5), match='From '>
内部的には、これらの関数は単にパターンオブジェクトを作成し、その上で適切なメソッドを呼び出すだけです。 また、コンパイルされたオブジェクトをキャッシュに格納するため、同じREを使用する今後の呼び出しでは、パターンを何度も解析する必要はありません。
これらのモジュールレベルの関数を使用する必要がありますか、それともパターンを取得してそのメソッドを自分で呼び出す必要がありますか? ループ内で正規表現にアクセスしている場合、それをプリコンパイルすると、いくつかの関数呼び出しが節約されます。 ループの外側では、内部キャッシュのおかげで大きな違いはありません。
コンパイルフラグ
コンパイルフラグを使用すると、正規表現の動作のいくつかの側面を変更できます。 フラグは、 re モジュールで、IGNORECASE
などの長い名前とI
などの短い1文字の形式の2つの名前で使用できます。 (Perlのパターン修飾子に精通している場合、1文字の形式は同じ文字を使用します。たとえば、 re.VERBOSE の短い形式は re.X です。)複数フラグは、ビット単位のOR演算によって指定できます。 re.I | re.M
は、たとえば、I
フラグとM
フラグの両方を設定します。
使用可能なフラグの表と、それに続く各フラグの詳細な説明を次に示します。
国旗 | 意味 |
---|---|
ASCII 、A
|
\w 、\b 、\s 、\d のようないくつかのエスケープは、それぞれのプロパティを持つASCII文字でのみ一致します。
|
DOTALL 、S
|
. を改行を含むすべての文字と一致させます。
|
IGNORECASE 、I
|
大文字と小文字を区別しない一致を実行します。 |
LOCALE 、L
|
ロケール対応の一致を実行します。 |
MULTILINE 、M
|
^ および$ に影響する複数行のマッチング。
|
VERBOSE 、X (「拡張」の場合)
|
よりクリーンで理解しやすいように編成できる詳細なREを有効にします。 |
- I
IGNORECASE
- 大文字と小文字を区別しないマッチングを実行します。 文字クラスとリテラル文字列は、大文字と小文字を区別せずに文字と一致します。 たとえば、
[A-Z]
は小文字にも一致します。ASCII
フラグを使用して非ASCII一致を無効にしない限り、完全なUnicode一致も機能します。 Unicodeパターン[a-z]
または[A-Z]
をIGNORECASE
フラグと組み合わせて使用すると、52個のASCII文字と4個の追加の非ASCII文字に一致します。 U + 0130、上にドットが付いたラテン大文字I)、「ı」(U + 0131、ドットなしのラテン小文字i)、「ſ」(U + 017F、ラテン小文字の長いs)、および「K」(U + 212A 、ケルビン記号)。Spam
は、'Spam'
、'spam'
、'spAM'
、または'ſpam'
と一致します(後者はUnicodeモードでのみ一致します)。 この小文字は、現在のロケールを考慮していません。LOCALE
フラグも設定すると表示されます。
- L
LOCALE \w
、\W
、\b
、\B
、および大文字と小文字を区別しないマッチングを、Unicodeデータベースではなく現在のロケールに依存させます。ロケールは、言語の違いを考慮したプログラムの作成を支援することを目的としたCライブラリの機能です。 たとえば、エンコードされたフランス語のテキストを処理している場合、単語に一致するように
\w+
を記述できるようにする必要がありますが、\w
はの文字クラス[A-Za-z]
にのみ一致します。バイトパターン;é
またはç
に対応するバイトとは一致しません。 システムが適切に構成されていて、フランス語のロケールが選択されている場合、特定のC関数は、é
に対応するバイトも文字と見なす必要があることをプログラムに通知します。 正規表現をコンパイルするときにLOCALE
フラグを設定すると、コンパイルされたオブジェクトは\w
に対してこれらのC関数を使用します。 これは遅くなりますが、\w+
が期待どおりにフランス語の単語と一致することも可能になります。 Python 3では、ロケールメカニズムの信頼性が非常に低く、一度に1つの「カルチャ」のみを処理し、8ビットロケールでのみ機能するため、このフラグの使用は推奨されていません。 Unicodeマッチングは、Python 3でUnicode(str)パターンに対してデフォルトですでに有効になっており、さまざまなロケール/言語を処理できます。
- M
MULTILINE (
^
と$
はまだ説明されていません。これらは、セクションその他のメタ文字で紹介されます。)通常、
^
は文字列の先頭でのみ一致し、$
は文字列の末尾で、文字列の末尾の改行(存在する場合)の直前でのみ一致します。 このフラグを指定すると、^
は、文字列の先頭と、各改行の直後の文字列内の各行の先頭で一致します。 同様に、$
メタ文字は、文字列の終わりと各行の終わり(各改行の直前)のいずれかで一致します。
- S
DOTALL
'.'
特殊文字を改行を含むすべての文字と完全に一致させます。 このフラグがないと、'.'
は改行を除くすべてのと一致します。
- A
ASCII
\w
、\W
、\b
、\B
、\s
、\S
に完全ではなくASCIIのみのマッチングを実行させるUnicodeマッチング。 これはUnicodeパターンでのみ意味があり、バイトパターンでは無視されます。
- X
VERBOSE このフラグを使用すると、フォーマット方法の柔軟性を高めることで、より読みやすい正規表現を記述できます。 このフラグが指定されている場合、RE文字列内の空白は無視されます。ただし、空白が文字クラス内にあるか、エスケープされていない円記号が前に付いている場合を除きます。 これにより、REをより明確に整理してインデントできます。 このフラグを使用すると、エンジンによって無視されるコメントをRE内に配置することもできます。 コメントは、文字クラスに含まれていないか、エスケープされていない円記号が前に付いている
'#'
でマークされています。たとえば、 re.VERBOSE を使用するREは次のとおりです。 どれだけ読みやすいかわかりますか?
charref = re.compile(r""" &[#] # Start of a numeric entity reference ( 0[0-7]+ # Octal form | [0-9]+ # Decimal form | x[0-9a-fA-F]+ # Hexadecimal form ) ; # Trailing semicolon """, re.VERBOSE)
詳細設定がない場合、REは次のようになります。
charref = re.compile("&#(0[0-7]+" "|[0-9]+" "|x[0-9a-fA-F]+);")
上記の例では、Pythonの文字列リテラルの自動連結を使用してREを細かく分割していますが、 re.VERBOSE を使用したバージョンよりも理解が困難です。
より多くのパターンパワー
これまで、正規表現の機能の一部のみを取り上げてきました。 このセクションでは、いくつかの新しいメタ文字と、グループを使用して一致したテキストの部分を取得する方法について説明します。
その他のメタ文字
まだカバーしていないメタ文字がいくつかあります。 それらのほとんどは、このセクションでカバーされます。
議論される残りのメタ文字のいくつかは、ゼロ幅アサーションです。 それらはエンジンをストリングを通して前進させません。 代わりに、文字をまったく消費せず、単に成功または失敗します。 たとえば、\b
は、現在の位置が単語の境界にあるというアサーションです。 \b
によって位置がまったく変更されません。 これは、ゼロ幅のアサーションが繰り返されないことを意味します。これは、特定の場所で1回一致する場合、明らかに無限の回数一致する可能性があるためです。
|
交互、または「または」演算子。 A および B が正規表現の場合、
A|B
は、 A または B のいずれかに一致する任意の文字列に一致します。|
は、複数文字の文字列を交互に使用する場合に適切に機能するように、優先順位が非常に低くなっています。Crow|Servo
は、'Cro'
、'w'
または'S'
、および'ervo'
。リテラル
'|'
と一致させるには、\|
を使用するか、[|]
のように文字クラスで囲みます。^
行頭で一致します。
MULTILINE
フラグが設定されていない限り、これは文字列の先頭でのみ一致します。MULTILINE
モードでは、これは文字列内の各改行の直後にも一致します。たとえば、
From
という単語を行の先頭でのみ一致させたい場合、使用するREは^From
です。>>> print(re.search('^From', 'From Here to Eternity')) <re.Match object; span=(0, 4), match='From'> >>> print(re.search('^From', 'Reciting From Memory')) None
リテラル
'^'
と一致させるには、\^
を使用します。$
文字列の終わり、または改行文字が後に続く任意の場所として定義される行の終わりで一致します。
>>> print(re.search('}$', '{block}')) <re.Match object; span=(6, 7), match='}'> >>> print(re.search('}$', '{block} ')) None >>> print(re.search('}$', '{block}\n')) <re.Match object; span=(6, 7), match='}'>
リテラル
'$'
と一致させるには、\$
を使用するか、[$]
のように文字クラスで囲みます。\A
文字列の先頭でのみ一致します。
MULTILINE
モードでない場合、\A
と^
は実質的に同じです。MULTILINE
モードでは、これらは異なります。\A
は文字列の先頭でのみ一致しますが、^
は改行文字に続く文字列内の任意の場所で一致する可能性があります。\Z
文字列の最後でのみ一致します。
\b
単語の境界。 これは、単語の最初または最後でのみ一致する幅ゼロのアサーションです。 単語は英数字のシーケンスとして定義されるため、単語の終わりは空白または英数字以外の文字で示されます。
次の例は、完全な単語である場合にのみ
class
に一致します。 別の単語に含まれている場合は一致しません。>>> p = re.compile(r'\bclass\b') >>> print(p.search('no class at all')) <re.Match object; span=(3, 8), match='class'> >>> print(p.search('the declassified algorithm')) None >>> print(p.search('one subclass is')) None
この特別なシーケンスを使用するときに覚えておくべき2つの微妙な点があります。 まず、これはPythonの文字列リテラルと正規表現シーケンスの間の最悪の衝突です。 Pythonの文字列リテラルでは、
\b
はバックスペース文字、ASCII値8です。 生の文字列を使用していない場合、Pythonは\b
をバックスペースに変換し、REは期待どおりに一致しません。 次の例は前のREと同じように見えますが、RE文字列の前の'r'
が省略されています。>>> p = re.compile('\bclass\b') >>> print(p.search('no class at all')) None >>> print(p.search('\b' + 'class' + '\b')) <re.Match object; span=(0, 7), match='\x08class\x08'>
次に、このアサーションを使用しない文字クラス内では、Pythonの文字列リテラルとの互換性のために、
\b
はバックスペース文字を表します。\B
別のゼロ幅アサーション。これは
\b
の反対であり、現在の位置が単語の境界にない場合にのみ一致します。
グループ化
多くの場合、REが一致したかどうかだけでなく、より多くの情報を取得する必要があります。 正規表現は、関心のあるさまざまなコンポーネントに一致するいくつかのサブグループに分割されたREを作成することにより、文字列を分析するためによく使用されます。 たとえば、RFC-822ヘッダー行は、次のように':'
で区切られたヘッダー名と値に分割されます。
From: [email protected]
User-Agent: Thunderbird 1.5.0.9 (X11/20061227)
MIME-Version: 1.0
To: [email protected]
これは、ヘッダー行全体に一致し、ヘッダー名に一致する1つのグループと、ヘッダーの値に一致する別のグループを持つ正規表現を記述することで処理できます。
グループは、'('
、')'
メタ文字でマークされます。 '('
と')'
は、数式の場合とほとんど同じ意味です。 それらはそれらの中に含まれる式をグループ化し、*
、+
、?
、{m,n}
。 たとえば、(ab)*
は、ab
の0回以上の繰り返しに一致します。
>>> p = re.compile('(ab)*')
>>> print(p.match('ababababab').span())
(0, 10)
'('
、')'
で示されたグループも、一致するテキストの開始インデックスと終了インデックスをキャプチャします。 これは、 group()、 start()、 end()、および span()に引数を渡すことで取得できます。 グループには0から始まる番号が付けられます。 グループ0は常に存在します。 これはRE全体であるため、 match object メソッドはすべて、デフォルトの引数としてグループ0を持ちます。 後で、一致するテキストのスパンをキャプチャしないグループを表現する方法を説明します。
>>> p = re.compile('(a)b')
>>> m = p.match('ab')
>>> m.group()
'ab'
>>> m.group(0)
'ab'
サブグループには、左から右、1から上に番号が付けられます。 グループはネストできます。 数を決定するには、左から右に向かって、開き括弧の文字を数えます。
>>> p = re.compile('(a(b)c)d')
>>> m = p.match('abcd')
>>> m.group(0)
'abcd'
>>> m.group(1)
'abc'
>>> m.group(2)
'b'
group()には、一度に複数のグループ番号を渡すことができます。その場合、それらのグループに対応する値を含むタプルが返されます。
>>> m.group(2,1,2)
('b', 'abc', 'b')
groups()メソッドは、1からいくつまでのすべてのサブグループの文字列を含むタプルを返します。
>>> m.groups()
('abc', 'b')
パターン内の後方参照を使用すると、以前のキャプチャグループのコンテンツも文字列内の現在の場所にある必要があることを指定できます。 たとえば、\1
は、グループ1の正確な内容が現在の位置にある場合は成功し、それ以外の場合は失敗します。 Pythonの文字列リテラルもバックスラッシュとそれに続く数字を使用して、文字列に任意の文字を含めることができることを忘れないでください。したがって、REに後方参照を組み込む場合は、必ず生の文字列を使用してください。
たとえば、次のREは、文字列内の二重の単語を検出します。
>>> p = re.compile(r'\b(\w+)\s+\1\b')
>>> p.search('Paris in the the spring').group()
'the the'
このような後方参照は、文字列を検索するだけでは役に立たないことがよくあります。この方法でデータを繰り返すテキスト形式はほとんどありませんが、文字列の置換を実行するときに非常に役立つことがすぐにわかります。 。
非キャプチャおよび名前付きグループ
複雑なREは、対象のサブストリングをキャプチャするため、およびRE自体をグループ化および構造化するために、多くのグループを使用する場合があります。 複雑なREでは、グループ番号を追跡することが困難になります。 この問題に役立つ2つの機能があります。 どちらも正規表現の拡張に共通の構文を使用しているため、最初にそれを見ていきます。
Perl 5は、標準の正規表現に強力に追加されていることでよく知られています。 これらの新機能のために、Perl開発者は、Perlの正規表現を標準のREと紛らわしく異なるものにすることなく、新しいシングルキーストロークメタ文字または\
で始まる新しい特別なシーケンスを選択できませんでした。 たとえば、新しいメタ文字として&
を選択した場合、古い式では、&
は通常の文字であり、\&
または[ X187X] 。
Perl開発者が選択した解決策は、拡張構文として(?...)
を使用することでした。 ?
は繰り返すものがないため、括弧の直後の?
は構文エラーであり、互換性の問題は発生しませんでした。 ?
の直後の文字は、使用されている拡張子を示しているため、(?=foo)
は1つ(ポジティブルックアヘッドアサーション)であり、(?:foo)
は別のもの(非キャプチャグループ)です。部分式foo
を含む)。
Pythonは、Perlの拡張機能のいくつかをサポートし、Perlの拡張機能構文に拡張機能構文を追加します。 疑問符の後の最初の文字がP
の場合、それがPythonに固有の拡張機能であることがわかります。
一般的な拡張構文について見てきたので、複雑なREのグループでの作業を簡素化する機能に戻ることができます。
正規表現の一部を示すためにグループを使用したいが、グループの内容を取得することに興味がない場合があります。 非キャプチャグループ(?:...)
を使用して、この事実を明示的にすることができます。ここで、...
を他の正規表現に置き換えることができます。
>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")
>>> m.groups()
()
グループが一致したものの内容を取得できないという事実を除いて、非キャプチャーグループはキャプチャーグループとまったく同じように動作します。 その中に何でも入れて、*
などの繰り返しメタ文字で繰り返し、他のグループ(キャプチャまたは非キャプチャ)内にネストすることができます。 (?:...)
は、他のすべてのグループの番号付け方法を変更せずに新しいグループを追加できるため、既存のパターンを変更する場合に特に便利です。 キャプチャするグループとキャプチャしないグループの間で検索のパフォーマンスに違いはないことに注意してください。 どちらの形式も他の形式よりも高速ではありません。
より重要な機能は名前付きグループです。グループを番号で参照する代わりに、グループを名前で参照できます。
名前付きグループの構文は、Python固有の拡張機能の1つです:(?P<name>...)
。 name は、明らかにグループの名前です。 名前付きグループは、キャプチャグループとまったく同じように動作し、さらに名前をグループに関連付けます。 グループのキャプチャを処理する match object メソッドはすべて、グループを番号で参照する整数、または目的のグループの名前を含む文字列のいずれかを受け入れます。 名前付きグループには引き続き番号が付けられるため、次の2つの方法でグループに関する情報を取得できます。
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'
名前付きグループは、番号を覚える代わりに、覚えやすい名前を使用できるので便利です。 imaplib モジュールのREの例を次に示します。
InternalDate = re.compile(r'INTERNALDATE "'
r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
r'(?P<year>[0-9][0-9][0-9][0-9])'
r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
r'"')
グループ9を取得することを覚えておくよりも、m.group('zonem')
を取得する方が明らかにはるかに簡単です。
(...)\1
などの式の後方参照の構文は、グループの番号を参照します。 当然、番号の代わりにグループ名を使用するバリアントがあります。 これは別のPython拡張機能です。(?P=name)
は、 name というグループの内容が現在の時点で再び一致する必要があることを示します。 二重単語を検索するための正規表現\b(\w+)\s+\1\b
は、\b(?P<word>\w+)\s+(?P=word)\b
と書くこともできます。
>>> p = re.compile(r'\b(?P<word>\w+)\s+(?P=word)\b')
>>> p.search('Paris in the the spring').group()
'the the'
先読みアサーション
もう1つのゼロ幅アサーションは、先読みアサーションです。 先読みアサーションは、ポジティブ形式とネガティブ形式の両方で利用でき、次のようになります。
(?=...)
- ポジティブな先読みアサーション。 これは、ここでは
...
で表される、含まれている正規表現が現在の場所で正常に一致する場合に成功し、それ以外の場合は失敗します。 ただし、含まれている式が試行されると、マッチングエンジンはまったく進みません。 パターンの残りの部分は、アサーションが開始された場所で正しく試行されます。 (?!...)
- 否定的な先読みアサーション。 これは肯定的な主張の反対です。 含まれている式が文字列の現在の位置で一致しない場合は成功します。
これを具体的にするために、先読みが役立つ場合を見てみましょう。 ファイル名に一致する単純なパターンを考えて、.
で区切られたベース名と拡張子に分割します。 たとえば、news.rc
では、news
がベース名であり、rc
がファイル名の拡張子です。
これに一致するパターンは非常に単純です。
.*[.].*$
.
はメタ文字であるため、特別に処理する必要があることに注意してください。そのため、特定の文字にのみ一致するように文字クラス内にあります。 末尾の$
にも注意してください。 これは、文字列の残りすべてを拡張機能に含める必要があることを確認するために追加されます。 この正規表現は、foo.bar
とautoexec.bat
とsendmail.cf
とprinters.conf
に一致します。
ここで、問題を少し複雑にすることを検討してください。 拡張子がbat
ではないファイル名を照合したい場合はどうなりますか? いくつかの誤った試み:
.*[.][^b].*$
上記の最初の試みは、拡張子の最初の文字がb
でないことを要求することにより、bat
を除外しようとします。 パターンもfoo.bar
と一致しないため、これは間違っています。
.*[.]([^b]..|.[^a].|..[^t])$
次のいずれかのケースに一致するように要求して最初のソリューションにパッチを適用しようとすると、式が乱雑になります。拡張機能の最初の文字がb
ではありません。 2番目の文字はa
ではありません。 または、3番目の文字がt
ではありません。 これはfoo.bar
を受け入れ、autoexec.bat
を拒否しますが、3文字の拡張子が必要であり、sendmail.cf
などの2文字の拡張子を持つファイル名は受け入れません。 パターンを修正するために、パターンを再び複雑にします。
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
3回目の試行では、sendmail.cf
など、3文字より短い拡張子を一致させるために、2番目と3番目の文字がすべてオプションになります。
パターンが非常に複雑になり、読みにくく、理解しにくくなっています。 さらに悪いことに、問題が変化し、bat
とexe
の両方を拡張機能として除外したい場合、パターンはさらに複雑で混乱を招きます。
ネガティブな先読みは、このすべての混乱を切り抜けます。
.*[.](?!bat$)[^.]*$
負の先読みは、次のことを意味します。式bat
がこの時点で一致しない場合は、パターンの残りの部分を試してください。 bat$
が一致する場合、パターン全体が失敗します。 末尾の$
は、拡張子がbat
でのみ始まるsample.batch
のようなものが許可されるようにするために必要です。 [^.]*
は、ファイル名に複数のドットがある場合にパターンが機能することを確認します。
別のファイル名拡張子を除外するのは簡単になりました。 アサーション内の代替としてそれを追加するだけです。 次のパターンは、bat
またはexe
のいずれかで終わるファイル名を除外します。
.*[.](?!bat$|exe$)[^.]*$
文字列の変更
これまでは、静的文字列に対して検索を実行してきました。 正規表現は、次のパターンメソッドを使用して、さまざまな方法で文字列を変更するためにも一般的に使用されます。
メソッド/属性 | 目的 |
---|---|
split()
|
文字列をリストに分割し、REが一致する場所で分割します |
sub()
|
REが一致するすべての部分文字列を検索し、それらを別の文字列に置き換えます |
subn()
|
sub() と同じことを行いますが、新しい文字列と置換の数を返します
|
文字列の分割
パターンの split()メソッドは、REが一致する場所で文字列を分割し、ピースのリストを返します。 これは、文字列の split()メソッドに似ていますが、分割できる区切り文字の一般性がはるかに高くなります。 文字列split()
は、空白または固定文字列による分割のみをサポートします。 ご想像のとおり、モジュールレベルの re.split()関数もあります。
- .split(string[, maxsplit=0])
- string を正規表現の一致で分割します。 REでキャプチャ括弧が使用されている場合、その内容も結果リストの一部として返されます。 maxsplit がゼロ以外の場合、最大で maxsplit 分割が実行されます。
maxsplit の値を渡すことにより、作成される分割の数を制限できます。 maxsplit がゼロ以外の場合、最大で maxsplit の分割が行われ、文字列の残りの部分がリストの最後の要素として返されます。 次の例では、区切り文字は英数字以外の文字の任意のシーケンスです。
>>> p = re.compile(r'\W+')
>>> p.split('This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
>>> p.split('This is a test, short and sweet, of split().', 3)
['This', 'is', 'a', 'test, short and sweet, of split().']
区切り文字間のテキストが何であるかだけでなく、区切り文字が何であるかを知る必要がある場合もあります。 REでキャプチャ括弧が使用されている場合、それらの値もリストの一部として返されます。 次の呼び出しを比較します。
>>> p = re.compile(r'\W+')
>>> p2 = re.compile(r'(\W+)')
>>> p.split('This... is a test.')
['This', 'is', 'a', 'test', '']
>>> p2.split('This... is a test.')
['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
モジュールレベルの関数 re.split()は、最初の引数として使用されるREを追加しますが、それ以外は同じです。
>>> re.split(r'[\W]+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'([\W]+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'[\W]+', 'Words, words, words.', 1)
['Words', 'words, words.']
検索と置換
もう1つの一般的なタスクは、パターンに一致するものをすべて見つけて、それらを別の文字列に置き換えることです。 sub()メソッドは、文字列または関数のいずれかである置換値と、処理される文字列を受け取ります。
- .sub(replacement, string[, count=0])
string のREの左端の重複しないオカレンスを置換 replacement で置き換えて取得した文字列を返します。 パターンが見つからない場合、 string は変更されずに返されます。
オプションの引数 count は、置き換えられるパターンオカレンスの最大数です。 count は負でない整数でなければなりません。 デフォルト値の0は、すべてのオカレンスを置き換えることを意味します。
sub()メソッドを使用する簡単な例を次に示します。 色の名前をcolour
という単語に置き換えます。
>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
subn()メソッドは同じ作業を行いますが、新しい文字列値と実行された置換の数を含む2タプルを返します。
>>> p = re.compile('(blue|white|red)')
>>> p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)
>>> p.subn('colour', 'no colours at all')
('no colours at all', 0)
空の一致は、前の空の一致に隣接していない場合にのみ置き換えられます。
>>> p = re.compile('x*')
>>> p.sub('-', 'abxd')
'-a-b--d-'
replace が文字列の場合、その中の円記号エスケープはすべて処理されます。 つまり、\n
は単一の改行文字に変換され、\r
はキャリッジリターンに変換されます。 \&
などの不明なエスケープはそのままにしておきます。 \6
などの後方参照は、RE内の対応するグループと一致する部分文字列に置き換えられます。 これにより、元のテキストの一部を結果の置換文字列に組み込むことができます。
この例は、単語section
の後に{
、}
で囲まれた文字列を照合し、section
をsubsection
に変更します。
>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First} section{second}')
'subsection{First} subsection{second}'
(?P<name>...)
構文で定義されているように、名前付きグループを参照するための構文もあります。 \g<name>
は、name
という名前のグループと一致するサブストリングを使用し、\g<number>
は対応するグループ番号を使用します。 したがって、\g<2>
は\2
と同等ですが、\g<2>0
などの置換文字列ではあいまいではありません。 (\20
は、グループ20への参照として解釈され、グループ2への参照の後にリテラル文字'0'
が続くものではありません。)次の置換はすべて同等ですが、3つのバリエーションすべてを使用します。置換文字列。
>>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<1>}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<name>}','section{First}')
'subsection{First}'
replace も関数にすることができ、さらに細かく制御できます。 replace が関数の場合、 pattern が重複しないたびに関数が呼び出されます。 呼び出しごとに、関数には一致の match object 引数が渡され、この情報を使用して目的の置換文字列を計算して返すことができます。
次の例では、置換関数が10進数を16進数に変換します。
>>> def hexrepl(match):
... "Return the hex string for a decimal number"
... value = int(match.group())
... return hex(value)
...
>>> p = re.compile(r'\d+')
>>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
'Call 0xffd2 for printing, 0xc000 for user code.'
モジュールレベルの re sub()関数を使用する場合、パターンは最初の引数として渡されます。 パターンは、オブジェクトまたは文字列として提供できます。 正規表現フラグを指定する必要がある場合は、最初のパラメーターとしてパターンオブジェクトを使用するか、パターン文字列に埋め込まれた修飾子を使用する必要があります。 sub("(?i)b+", "x", "bbbb BBBB")
は'x x'
を返します。
一般的な問題
正規表現は一部のアプリケーションにとって強力なツールですが、ある意味ではその動作が直感的ではなく、場合によっては期待どおりに動作しないことがあります。 このセクションでは、最も一般的な落とし穴のいくつかを指摘します。
文字列メソッドを使用する
re モジュールの使用が間違っている場合があります。 固定文字列または単一の文字クラスに一致し、 re 機能( IGNORECASE フラグなど)を使用していない場合、正規表現の全機能が使用される可能性があります。必要ありません。 文字列には、固定文字列を使用して操作を実行するためのいくつかのメソッドがあり、実装は、大きくて一般化された正規表現エンジンではなく、目的に合わせて最適化された単一の小さなCループであるため、通常ははるかに高速です。
1つの例は、単一の固定文字列を別の文字列に置き換えることです。 たとえば、word
をdeed
に置き換えることができます。 re.sub()はこれに使用する関数のようですが、 replace()メソッドを検討してください。 replace()
は単語内のword
も置き換え、swordfish
をsdeedfish
に変換しますが、単純なRE word
はそれを実行することに注意してください。それも。 (単語の一部で置換が実行されないようにするには、word
の両側に単語の境界があることを要求するために、パターンは\bword\b
である必要があります。 これはreplace()
の能力を超えた仕事をします。)
もう1つの一般的なタスクは、文字列から1文字が出現するたびに削除するか、別の1文字に置き換えることです。 re.sub('\n', ' ', S)
のようなものでこれを行うことができますが、 translate()は両方のタスクを実行でき、正規表現操作よりも高速になります。
つまり、 re モジュールに移る前に、より高速で単純な文字列メソッドで問題を解決できるかどうかを検討してください。
match()とsearch()
match()関数は、REが文字列の先頭で一致するかどうかのみをチェックし、 search()は文字列を前方にスキャンして一致するかどうかを確認します。 この違いを覚えておくことが重要です。 match()
は、0から始まる成功した一致のみを報告することを忘れないでください。 一致がゼロから始まらない場合、match()
は報告しません。
>>> print(re.match('super', 'superstition').span())
(0, 5)
>>> print(re.match('super', 'insuperable'))
None
一方、 search()は文字列を前方にスキャンし、最初に一致したものを報告します。
>>> print(re.search('super', 'superstition').span())
(0, 5)
>>> print(re.search('super', 'insuperable').span())
(2, 7)
re.match()を使い続けて、REの前に.*
を追加したくなることがあります。 この誘惑に抵抗し、代わりに re.search()を使用してください。 正規表現コンパイラは、一致するものを探すプロセスを高速化するために、REの分析を行います。 そのような分析の1つは、一致の最初の文字が何である必要があるかを把握します。 たとえば、Crow
で始まるパターンは、'C'
で始まるパターンと一致する必要があります。 分析により、エンジンは文字列をすばやくスキャンして開始文字を探し、'C'
が見つかった場合にのみ完全一致を試行します。
.*
を追加すると、この最適化が無効になり、文字列の最後までスキャンしてから、残りのREに一致するものを見つけるためにバックトラックする必要があります。 代わりに re.search()を使用してください。
貪欲対非貪欲
a*
のように正規表現を繰り返すと、結果として得られるアクションは、可能な限り多くのパターンを消費することです。 この事実は、HTMLタグを囲む山かっこなど、バランスの取れた区切り文字のペアを一致させようとしているときによく噛み付きます。 .*
の貪欲な性質のため、単一のHTMLタグを照合するための単純なパターンは機能しません。
>>> s = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print(re.match('<.*>', s).span())
(0, 32)
>>> print(re.match('<.*>', s).group())
<html><head><title>Title</title>
REは'<html>'
の'<'
と一致し、.*
は残りの文字列を消費します。 ただし、REにはまだまだ残っており、>
は文字列の最後で一致できないため、正規表現エンジンは、>
。 最終的な一致は、'<html>'
の'<'
から'</title>'
の'>'
まで拡張されますが、これはあなたが望むものではありません。
この場合の解決策は、 littleとして一致する非欲張り修飾子*?
、+?
、??
、または{m,n}?
を使用することです。 可能な限りテキスト。 上記の例では、最初の'<'
が一致した直後に'>'
が試行され、失敗すると、エンジンは一度に1文字ずつ進み、'>'
を毎回再試行します。ステップ。 これにより、適切な結果が得られます。
>>> print(re.match('<.*?>', s).group())
<html>
(HTMLまたはXMLを正規表現で解析するのは面倒であることに注意してください。 クイックアンドダーティパターンは一般的なケースを処理しますが、HTMLとXMLには、明らかな正規表現を壊す特殊なケースがあります。 考えられるすべてのケースを処理する正規表現を作成するまでに、パターンは非常に複雑になります。 このようなタスクには、HTMLまたはXMLパーサーモジュールを使用してください。)
re.VERBOSEの使用
ここまでで、正規表現は非常にコンパクトな表記法であることに気付いたと思いますが、それほど読みやすくはありません。 中程度の複雑さのREは、円記号、括弧、およびメタ文字の長いコレクションになり、読みにくく、理解しにくくなる可能性があります。
このようなREの場合、正規表現をより明確にフォーマットできるため、正規表現のコンパイル時に re.VERBOSE フラグを指定すると便利です。
re.VERBOSE
フラグにはいくつかの効果があります。 文字クラス内でがではない正規表現の空白は無視されます。 つまり、dog | cat
などの式は、読みにくいdog|cat
と同等ですが、[a b]
は、文字'a'
、'b'
と一致します。 ]、またはスペース。 さらに、RE内にコメントを入れることもできます。 コメントは、#
文字から次の改行まで拡張されます。 トリプルクォートされた文字列とともに使用すると、REをより適切にフォーマットできます。
pat = re.compile(r"""
\s* # Skip leading whitespace
(?P<header>[^:]+) # Header name
\s* : # Whitespace, and a colon
(?P<value>.*?) # The header's value -- *? used to
# lose the following trailing whitespace
\s*$ # Trailing whitespace to end-of-line
""", re.VERBOSE)
これは、以下よりもはるかに読みやすくなっています。
pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")
フィードバック
正規表現は複雑なトピックです。 このドキュメントはそれらを理解するのに役立ちましたか? 不明な部分や、ここで取り上げていない問題がありましたか? もしそうなら、作者に改善のための提案を送ってください。
正規表現に関する最も完全な本は、ほぼ間違いなく、O'Reillyによって発行されたJeffreyFriedlのMasteringRegularExpressionsです。 残念ながら、PerlとJavaの正規表現にのみ焦点を当てており、Pythonの資料はまったく含まれていないため、Pythonでのプログラミングのリファレンスとしては役立ちません。 (初版では、Pythonの削除されたregex
モジュールについて説明しましたが、あまり役に立ちません。)ライブラリからチェックアウトすることを検討してください。