正規表現、初心者向けのガイド。 パート2

このマニュアルの前半では、正規表現の可能性のほんの一部のみを明らかにしました。 2番目の大きな半分では、いくつかの新しいメタキャラクター、グループを使用して一致するテキストの一部を取得する方法、行を分割する、テキストの一部を検索および置換する方法について説明します。 最後に、よくある間違いについて少し話しましょう。

その他のメタキャラクター


まだ研究していないメタキャラクターがいくつかあります。 それらのほとんどについては、このセクションで説明します。

残りのメタキャラクターの一部は、 サイズゼロのステートメントです。 彼らはエンジンを呼び出してラインを通過せず、キャラクターをまったくカバーせず、成功または失敗のみが可能です。 たとえば、 \bは現在の位置が単語の境界上にあるというステートメントですが、 \b文字自体は位置を変更しません。 これは、サイズがゼロのステートメントを繰り返してはならないことを意味します。特定の場所で一度一致すると、明らかにこの場所に無限に対応するからです。

|
OR演算子に対応します。 AとBが正規表現の場合、 A|BはAまたはBに一致する任意の文字列に一致します。 文字列の複数の文字を代替するときに合理的に機能させるために、優先度は非常に低くなっています。 Crow | Servoは、 Cro('w' 'S')ervoではなく、 CrowまたはServoいずれかと一致するものを探します。

^
行の先頭でのみ一致を検索します。 前の部分で説明したように、 MULTILINEフラグがMULTILINE場合、改行文字の後の各部分について比較が行われます。

たとえば、先頭にFrom行のみを検索する場合、 ^From正規表現に書き込まれます。

>>> 印刷 re検索 '^ From''From Here to Eternity'
< _sre。 0xのSRE_Match オブジェクト ... >
>>> 印刷 re検索 '^ From''Reciting From Memory'
なし


$
^と同じですが、行の最後で、実際の行の終わり、または新しい行の文字によって決定されます。

>>> 印刷 re検索 '} $''{block}'
< _sre。 0xのSRE_Match オブジェクト ... >
>>> 印刷 re検索 '} $''{block}'
なし
>>> 印刷 re検索 '} $''{block} \ n '
< _sre。 0xのSRE_Match オブジェクト ... >


\A
行の先頭でのみ一致します。つまり、 ^と同じMULTILINEが、 MULTILINEフラグには依存しません。

\Z
行末でのみ一致します。つまり、 $と同じMULTILINEが、 MULTILINEフラグには依存しません。

\b
単語の境界。 単語は数字や文字の文字のシーケンスとして定義されるため、単語の境界はスペースまたはリストされている文字に関係のない文字を表します。

次の例では、単語classが単一の単語である場合にのみ単語class検索します。 別の単語に含まれている場合、一致するものはありません。

>>> p = reコンパイル r ' \ b class \ b '
>>> 印刷 p。 検索 「クラスがまったくない」
< _sre。 0xのSRE_Match オブジェクト ... >
>>> 印刷 p。 検索 「機密解除されたアルゴリズム」
なし
>>> 印刷 p。 検索 「1つのサブクラスは」
なし


この特別なシーケンスを使用する際に覚えておくべき2つの微妙な点があります。 まず、これはPython文字列リテラルと正規表現シーケンスの最悪の衝突の1つです。Python文字列リテラルでは、 \bはバックスペース文字、ASCIIは8です。生の文字列を使用しない場合、Pythonは\bをバックスペースに変換します。あなたの正規表現は意図したものではありません:

>>> p = reコンパイル ' \ b class \ b '
>>> 印刷 p。 検索 「クラスがまったくない」
なし
>>> 印刷 p。 検索 ' \ b ' + 'class' + ' \ b '
< _sre。 0xのSRE_Match オブジェクト ... >


第二に、文字列リテラルとの互換性のための\bの組み合わせ、Pythonはバックスペース文字を表すため、この組み合わせを文字クラス内で使用することはできません。

\ B
単語の境界上ではなく現在の位置に対応する、前の組み合わせの反対。

グルーピング


多くの場合、RVが一致するかどうかを調べるだけでなく、より多くの情報が必要です。 正規表現は、クエリのさまざまなコンポーネントに対応するいくつかのサブグループに分割された正規表現を記述することにより、行をカットするためによく使用されます。 たとえば、RFC-822標準では、ヘッダーにはコロンで区切られたさまざまなフィールドが含まれています。

From: author@example.com
User-Agent: Thunderbird 1.5.0.9 (X11/20061227)
MIME-Version: 1.0
To: editor@example.com


これは、タイトルバー全体に一致する正規表現を記述することで処理でき、ヘッダーの名前に一致する1つのグループと、ヘッダーの値に一致する別のグループがあります。

グループは、括弧'(', ')'の形のメタキャラクターによって示されます。 '('および')'は、数式と同じ意味を持ちます。 それらに含まれる式をグループ化し、 *, +, ?などの繰り返し修飾子を使用してグループのコンテンツを繰り返すことができます。 および{m, n} 。 たとえば、 (ab)*は、0回以上のab繰り返しに一致します。

>>> p = reコンパイル '(ab)*'
>>> 印刷 p。 マッチ 'ababababab' スパン
0、10


括弧で囲まれたグループは、一致するテキストの開始インデックスと終了インデックスもキャプチャします。 これは、 group(), start(), end()およびspan()引数を渡すことで取得できます。 グループには0から始まる番号が付けられます。グループ0は常に存在します。これは正規表現全体であるため、 MatchObjectメソッドには常にデフォルト引数として0が含まれます。

>>> p = reコンパイル '(a)b'
>>> m = p。 一致 'ab'
>>> m。 グループ
「ab」
>>> m。 グループ 0
「ab」


サブグループには、左から右へ、1から順に番号が付けられます。 グループはネストできます。 添付ファイルの数を決定するために、左括弧から右へ左角括弧の文字を数えるだけです:

>>> p = reコンパイル '(a(b)c)d'
>>> m = p。 一致 'abcd'
>>> m。 グループ 0
「abcd」
>>> m。 グループ 1
「abc」
>>> m。 グループ 2
「b」


group()は、1つのリクエストで複数のグループ番号を同時に取得でき、対応するグループの値を含むタプルが返されます。

>>> m。 グループ 2、1、2
'b''abc''b'


groups()メソッドは、1番目から始まるすべてのサブグループの行のタプルを返します。

>>> m。 グループ
'abc''b'


テンプレートのバックリンクを使用すると、以前にキャプチャしたグループのコンテンツも行の現在の位置で見つける必要があることを示すことができます。 たとえば、 \1は、グループ1のコンテンツが現在の位置で正確に繰り返されるという事実に対応します。

たとえば、次のRWは、行内の重複する単語を検出します。

>>> p = reコンパイル r '( \ b \ w +) \ s + \ 1 '
>>> p。 検索 「春のパリ」 グループ
「the」


このようなバックリンクは、文字列の検索にはあまり役立ちませんが、文字列の置換を行うときに非常に役立つことがすぐにわかります。

コンテンツキャプチャグループと名前付きグループ

正規表現では、必要なサブストリングのキャプチャと、PB自体のグループ化と構造化の両方に、多くのグループを使用できます。 複雑な正規表現では、グループ番号を追跡することが難しくなります。 この問題に対処するのに役立つ2つの機能があります。 どちらも正規表現を拡張するために共通の構文を使用しているため、最初に検討します。

Perl 5は標準の正規表現にいくつかの追加機能を追加し、 reモジュールはそれらのほとんどをサポートしています。 Perlの正規表現が標準の正規表現と混同されるように、新しい機能を導入するために、新しい単一文字のメタキャラクターまたは新しいバックスラッシュシーケンスを選択することは困難です。 たとえば、新しいメタ文字として&を選択した場合、古い正規表現はそれを通常の文字として受け入れ、 \&または[&]エスケープできませんでした。

Perl開発者が選択したソリューションは、構文拡張として(?...)を使用することでした。 通常のRVの場合、括弧の後の疑問符は構文エラー? 繰り返す必要がないため、これにより互換性の問題が生じることはありません。 直後の記号? どの拡張子が使用されている(?=foo)示すので、 (?=foo)これは1つ(プレビューに関する肯定的な声明)であり、 (?:foo)それは何か(サブ表現fooを含むコンテンツをキャプチャしないグループ)です。

Pythonは、Perl拡張構文に独自の拡張機能を追加しました。 疑問符の後の最初の文字がP場合、これはPython固有の拡張機能が使用されていることを意味します。 現在、このような拡張機能が2つあります。( ?P<some_name>... )は名前付きグループを定義し、( ?P=some_name )はそのための?P=some_nameとして機能します。 Perl 5の将来のバージョンで異なる構文を使用する同様の機能が追加された場合、互換性のためにPython構文を維持しながら、 reモジュールは新しい構文をサポートするように変更されます。

グループを使用して正規表現の一部を収集する必要がある場合もありますが、グループのコンテンツを抽出する必要はありません。 コンテンツをキャプチャせずにグループを使用してこれを行うことができます: (?:...)...他の正規表現に置き換えることができます:

>>> m = reマッチ "([abc])+""abc"
>>> m。 グループ
「c」
>>> m = reマッチ "(?:[abc])+""abc"
>>> m。 グループ


グループが一致した内容を取得していないことを除いて、これらのグループは通常のグループと同じように動作します。 それらに何かを入れて、 *などの適切な文字で繰り返し、他のグループに挿入できます(データを収集するかどうか)。

より重要な機能はグループという名前です。番号でグループを参照する代わりに、これらのグループを名前で参照できます。

名前付きグループの構文は、特定のPython拡張機能の1つ(?P<some_name>...) :( (?P<some_name>...) 。 名前付きグループは通常のグループとまったく同じように動作しますが、それに加えて、何らかの名前に関連付けられています。 通常のグループに使用されたMatchObjectメソッドは、グループ番号を参照する番号と、必要なグループの名前を含む文字列の両方を受け入れます。 つまり、名前付きグループも番号を受け入れるため、次の2つの方法でグループ情報を取得できます。

>>> p = reコンパイル r '(?P <word> \ b \ w + \ b )'
>>> m = p。 検索 '((((句読点)))'
>>> m。 グループ 'word'
「たくさん」
>>> m。 グループ 1
「たくさん」


名前付きグループは、番号の代わりに覚えやすい名前を使用できるという点で便利です。 imaplibモジュールの正規表現の例を次にimaplibます。

InternalDate = reコンパイル r 'INTERNALDATE "'
r '(?P <日> [123] [0-9])-(?P <mon> [AZ] [az] [az])-'
r '(?P <年> [0-9] [0-9] [0-9] [0-9])'
r '(?P <時間> [0-9] [0-9]):(?P <分> [0-9] [0-9]):(?P <秒> [0-9] [ 0-9]) '
r '(?P <zonen> [-+])(?P <zoneh> [0-9] [0-9])(?P <zonem> [0-9] [0-9])'
r '"'


タイプ(...)\1正規表現のバックリンク構文は、グループ番号を参照します。 数字の代わりにグループ名を使用する方が自然です。 別のPython拡張機能:( (?P=name)は、名前付きグループの内容が現在の位置で再び一致する必要があることを示します。 重複する単語を見つけるための以前の正規表現(\b\w+)\s+\1は、 (?P<doble_word>\b\w+)\s+(?P=doble_word)と書くこともできます。

>>> p = reコンパイル r '(?P <word> \ b \ w +) \ s +(?P = word)'
>>> p。 検索 「春のパリ」 グループ
「the」


事前確認

小切手は正と負(後ろ向き)の形式で利用でき、次のようになります。

(?=...)
ポジティブチェック。 ここで...として表される、含まれている式が現在の位置に対応する場合に対応します。 ただし、含まれている式がテストされた後、比較エンジンはそれ以上進みません。 テンプレートの残りの部分は、承認が開始される場所の右側とさらに比較されます。

(?!...)
負のチェックは、含まれている式が文字列の現在の位置と一致しない場合に対応します。

具体的には、プレビューが役立つ場合を検討してください。 ファイル名を比較し、それを部分に分割するための単純なテンプレートを考えてみましょう:名前自体と拡張子、ドットで区切られています。

このような比較のテンプレートは非常に簡単です。

.*[.].*$

記号に注意してください. 同じ式で見られるように、ポイント自体がメタキャラクターであるため、特別な括弧が必要です。 最後の$にも注意してください。 残りの行が拡張機能に含まれるようにするために追加されます。

ここで、問題をもう少し広く考えてみましょう。 拡張子がbatはないすべてのファイルの名前を比較したい場合はどうしますか? いくつかの誤った試み:

.*[.][^b].*$
最初の試みは、最初の拡張文字がbではないことを要求することbatbatを除外することです。 テンプレートはfoo.barも除外するため、これは正しくありませfoo.bar

.*[.]([^b]..|.[^a].|..[^t])$
必要な文字の個別のタスクによって最初の解決策を修正することを決定した場合、式はさらに面倒になります。拡張子の最初の文字はbであってはなりません。 2番目はaではありません。 3番目はtではありません。 これにより、 foo.barが有効にfoo.barautoexec.batが拒否されますが、3文字の拡張子が必要であり、2文字のファイル名拡張子( sendmail.cfなど)では機能しません。 次に、この問題を解決するためにテンプレートを再度複雑にする必要があります。

.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
3回目の試行では、2文字目と3文字目をオプションにして、3文字より短い拡張子を比較できるようにします。

これでテンプレートの準備が整いました。読みやすく理解しにくいです。 さらに悪いことに、問題が変わり、batとexeの両方の拡張機能を除外する必要がある場合、テンプレートはさらに複雑でわかりにくくなります。

ネガティブリードチェックは、これらすべての問題を解決します。

.*[.](?!bat$).*$
ネガティブプレビューは、 bat表現が特定の位置と一致しない場合、テンプレートの残りを比較します。 一致するbat$場合、テンプレート全体は適切ではありません。 式を囲む$記号は、 sample.batchなどの式sample.batchれるために必要です。

別の拡張機能を除外することも簡単になりました。 同じステートメントに代替として追加するだけです。 次のパターンは、 batまたはexe拡張子で終わるファイル名を除外します。

.*[.](?!bat$|exe$).*$

行の変更


ここまでは、静的な文字列を検索しました。 正規表現は、次のテンプレートメソッドを使用して、さまざまな方法で文字列を変更するためにもよく使用されます。

メソッド/属性目的
分割()一致するリストに行を分割します
サブ()PBで一致するすべてのサブストリングを検索し、それらを別のストリングで置き換え
subn()sub()と同じですが、改行と置換の数を返します


改行

split()テンプレートメソッドは、PBに一致する部分に文字列を分割し、部分のリストを返します。 これは文字列メソッドsplit()に似ていますが、分割に使用される区切り文字の普遍性を提供します。 通常のsplit()は、空白文字または固定文字列のみで分割します。 予想どおり、モジュール関数re.split()ます。

.split(文字列[、maxsplit = 0])
正規表現の一致により文字列を区切ります。 PBにキャプチャブラケットがある場合、その内容も受信したリストの一部として返されます。 maxsplitゼロでない場合、 maxsplitパーティション以上はmaxsplitず、行の残りはリストの最後の要素として返されます。

次の例では、区切り文字は英数字以外の任意のシーケンスです。

>>> p = reコンパイル r ' \ W +'
>>> p。 split 'これはsplit()の短くて甘いテストです。'
[ 'This''is''a''test''short''and''sweet''of''split''' ]
>>> p。 split 'これはsplit()の短くて甘いテストです'3
[ 'This''is''a''split()の、短くて甘いテスト。' ]


区切り文字の間にあるテキストだけでなく、使用されている区切り文字を知る必要がある場合もあります。 RVにキャプチャブラケットがある場合、これらの値もリストの一部として返されます。 比較する:

>>> p = reコンパイル r ' \ W +'
>>> p2 = reコンパイル r '( \ W +)'
>>> p。 split 'これはテストです。'
[ 'This''is''a''test''' ]
>>> p2。 split 'これはテストです。'
[ 'This''...''is''''a''''test''。''' ]


最初の引数としてのre.split()モジュール関数はPBを受け取り、それ以外の場合も同様に動作します。

>>> resplit '[ \ W ] +''単語、単語、単語'
[ 'Words''words''words''' ]
>>> resplit '([ \ W ] +)''単語、単語、単語'
[ 'Words''、''words''、''words''。''' ]
>>> resplit '[ \ W ] +''単語、単語、単語。'1
[ 「言葉」「言葉、言葉。」 ]


検索と置換

もう1つの一般的なタスクは、パターンと一致するものをすべて検索し、それらを別の行に置き換えることです。 sub()メソッドは、引数として置換部分の値(文字列または関数のいずれか)と処理する文字列を取ります。

.sub(置換、文字列[、カウント= 0])
置換の結果の文字列を返します。 パターンが見つからない場合、文字列は変更されずに返されます。

追加の引数countは、置換可能な一致の最大数です。

sub()メソッドを使用した簡単な例。 色の名前は、 colour置き換えられcolour

>>> p = reコンパイル '(青|白|赤)'
>>> p。 sub 'color''blue socks and red shoes'
「色の靴下と色の靴」
>>> p。 sub 'color''blue socks and red shoes' 、count = 1
「色の靴下と赤い靴」


subn()メソッドも同じことを行いますが、新しい行と行われた置換の数を含むタプルを返します。

>>> p = reコンパイル '(青|白|赤)'
>>> p。 subn 'color''blue socks and red shoes'
「色の靴下と色の靴」2
>>> p。 subn '色''色なし'
「色なし」0


空の一致は、前の一致に隣接していない場合にのみ置き換えられます。

>>> p = reコンパイル 'x *'
>>> p。 sub '-''abxd'
「-abd-」


文字列が代替文字である場合、エスケープ文字がサポートされます。 したがって、 \nは単一の改行文字、 \rはキャリッジリターンなどです。 \6などのバックリンクは、PB内の対応するグループに一致するサブストリングに置き換えられます。 これにより、文字列を置換した結果に元のテキストの一部を含めることができます。

この例は、中括弧{, }部分に先行する行の部分の単語sectionに対応し、 sectionsubsection置き換えます。

>>> p = recompile 'セクション{([^}] *)}'re。VERBOSE
>>> p。 sub r 'subsection { \ 1 }''section {First} section {second}'
'サブセクション{First}サブセクション{second}'


名前付きグループを参照することもできます。 これを行うには、シーケンスの\g<...>使用し...グループの番号または名前は...として使用できます... \g<2>\2と同等ですが、 \g<2>0などの置換基では曖昧ではありません。 ( \20はグループ20への参照として解釈され、2番目のグループの後にリテラル '0'が続くと解釈されません。)次の操作は同等ですが、3つの異なる方法を使用します。

>>> p = recompile 'セクション{(?P <name> [^}] *)}'re。VERBOSE
>>> p。 sub r 'subsection { \ 1 }''section {First}'
「サブセクション{最初}」
>>> p。 sub r 'subsection { \ g <1>}''section {First}'
「サブセクション{最初}」
>>> p。 sub r 'subsection { \ g <name>}''section {First}'
「サブセクション{最初}」


代替は、より詳細に制御できる機能でもあります。 その場合、重複しないパターンの各ケースに対して関数が呼び出されます。 関数が呼び出されるMatchObjectMatchObjectが引数として渡されます。

次の例では、replace関数は10進数を16進数に変換します。

>>> def hexrepl マッチ
... 「10進数の16進文字列を返す」
... value = int match。group
... 16進数を 返す
...
>>> p = reコンパイル r ' \ d +'
>>> p。 sub hexrepl、 「印刷のために65490を呼び出し、ユーザーコードのために49152を呼び出します。」
「印刷のために0xffd2を呼び出し、ユーザーコードのために0xc000を呼び出します。」


一般的な問題


正規表現は一部のアプリケーションにとって強力なツールですが、いくつかの点でその動作は直感的ではなく、時には期待どおりに動作しません。 .


re . , re , . , .

, , , word deed . , , re.sub() , replace() . , replace() word , swordfish sdeedfish、しかし単純な正規表現でも同じことができます。(単語の一部で置換を実行しないようにするには、テンプレートにを含める必要があります\bword\b)。

別の一般的なタスクは、文字列から単一の文字を削除するか、別の文字に置き換えることです。のようなものにすることができますre.sub('\n', ' ', S)が、translate()メソッドは両方のタスクを処理し、正規表現よりも高速にします。

要するに、モジュールを使用する前にre、より高速で単純な文字列メソッドを使用して問題を解決できるかどうかを確認してください。

検索()と比較した一致()

この関数match()は、行search()全体で一致を検索する一方で、行の先頭で一致を検索します。この区別を念頭に置くことが重要です。

>>> print re . match ( 'super' , 'superstition' ) . span ( )
( 0 , 5 )
>>> print re . match ( 'super' , 'insuperable' )
None
>>> print re . search ( 'super' , 'superstition' ) . span ( )
( 0 , 5 )
>>> print re . search ( 'super' , 'insuperable' ) . span ( )
( 2 , 7 )


常にre.match()正規表現の前に追加するだけで使用したくなるかもしれません.*。この誘惑に抵抗し、代わりに使用してくださいre.search()。正規表現コンパイラは、RV分析を少し実行して、マッチングプロセスを高速化します。分析のタイプの1つは、最初に一致する文字を決定することです。たとえば、で始まるパターンとの一致Crowはで始まる必要があり'C'ます。この分析により、エンジンは最初の文字を探して行をすばやく実行し、文字「C」が見つかった場合にのみ完全な比較を開始するという事実につながります。

追加中.*行の終わりまでスキャンしてから、正規表現の残りの部分を比較するために戻ることにより、この最適化を無効にします。代わりに使用しますre.search()

貪欲vs非欲張り

のようなPBで繰り返される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 >


'<' — html, .* . '<' html ' >' /title , , , , .

, *?, +?, ?? {m,n}? , , . , '>' '<', , , '>' , , . :

>>> print re . match ( '<.*?>' , s ) . group ( )
< html >


(, HTML XML . - , . , . HTML XML .)

re.VERBOSE

PBのレコードは非常にコンパクトであることに気づいたかもしれませんが、読みにくい場合もあります。中程度の複雑さのRWは、スラッシュ、括弧、メタ文字の長いシーケンスである可能性があり、読み取りや理解が困難になります。

そのようなPBでは、正規表現をより明確にフォーマットできるため、正規表現のコンパイル時にVERBOSEフラグを指定すると便利です。

VERBOSEフラグにはいくつかの機能があります。文字クラス内にないPBのスペースは無視されます。これは、などの式はdog | cat読みにくい空白と同等ですdog|catが、[ab]は引き続き文字に一致することを意味します'a', 'b' . , , # . :

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コンパイル r " \ s *(?P <header> [^:] +)\ s * :(?P <value>。*?)\ s * $"


結論として


Re
Habrablog Regular Expressions Module Documentation 正規表現
エディター

Source: https://habr.com/ru/post/J115436/


All Articles