2.36. グループとアンカー

パターンは「この文字列が一致する」と判定するだけにとどまりません。一致した部分を取り出し、それぞれに名前を付けてアプリケーションに渡すこともできます。パターンの一部を括弧で囲むと キャプチャグループ になり、マッチオブジェクトは各グループを独立した部分文字列として公開します。

2.36.1. キャプチャグループ

パターンの任意の部分を (...) で囲むと、一致した内容をキャプチャできます:

>>> import re
>>> m = re.search(r'temp (\d+) at (\d+)s', 'temp 42 at 137s ok')
>>> m.group(0)
'temp 42 at 137s'
>>> m.group(1)
'42'
>>> m.group(2)
'137'
  • グループ 0 は常に一致全体を表します。

  • グループ 1、2、... はキャプチャされた部分文字列で、開き 括弧の位置に従って左から右に番号が付けられます。

  • match.group() を最後のグループより後ろのインデックスで呼び出すと IndexError が発生します。

よくあるパターンは「既知の構造に一致させ、変化する部分を整数としてキャプチャする」というものです:

def parse_temp(line):
    m = re.search(r'temp (\d+) at (\d+)s', line)
    if not m:
        return None
    return int(m.group(1)), int(m.group(2))

2.36.2. 非キャプチャグループ

括弧には、量指定子がグループ全体に適用されるように部分式を グループ化 する役割もあります。r'(ab)+' における唯一のグループ化の目的はこれであり、つまり「ab が 1 回以上」を意味します。ab がグループ 1 として現れるのは副次的な結果にすぎません。

キャプチャせずにグループ化するには (?:...) を使います:

>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'

非キャプチャグループは、パターンが構造のためにグループ化を使うものの、各部分を取り出すことには関心がない場合に、グループ番号を整然と保ちます。

2.36.3. アンカー

アンカーは文字に一致するのではなく、位置 に一致します。

  • ^ -- 文字列の先頭。

  • $ -- 文字列の末尾。

アンカーこそが re.match()re.search() の動作の違いを生むものです。re.match(p, s)re.search('^' + p, s) と同じで、パターンを位置 0 から始まるように強制します。パターンの末尾に $ を加えると、パターンは 文字列全体 に一致し、それ以外には一致しなくなります:

>>> re.search(r'^\d+$', '12345')
<match num=1>
>>> re.search(r'^\d+$', '12345 ok') is None
True

MicroPython の re における ^$ は、常に re.search() に渡された 文字列全体 の先頭と末尾を意味します。埋め込まれた各改行で一致させるための re.MULTILINE フラグは存在せず、$ は末尾の \n の直前の位置にも一致しません。つまり入力の絶対的な末尾でなければなりません。行単位の動作を得るには、まず入力を改行で分割し、各行に対してパターンを実行してください。

2.36.4. 文字集合

角括弧は明示的な文字の集合を定義します。一致では集合からちょうど 1 文字を消費します。

  • [abc] -- abc のいずれか 1 文字。

  • [a-z] -- a-z の範囲(両端を含む)の 1 文字。

  • [a-zA-Z0-9] -- 英字または数字。3 つの範囲を組み合わせたもの。

  • [^abc] -- abcいずれでもない 文字。^ は角括弧内の先頭の文字である場合にのみ否定の意味を持ちます。

例:

>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest').group(0)
'1A2B3C'
>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest') is None
True

リテラルのテキストが小文字であるため、最初の呼び出しは実際には None を返します。MicroPython の re には re.IGNORECASE フラグがありません。大文字小文字を区別せずに一致させるには、両方のケースを集合に書き込みます:

>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'

文字クラスの省略形(\d\s\w、およびそれらの否定形)は [...] の中でも使えます。[\w-] は「単語文字またはリテラルのダッシュ」を意味します。

2.36.5. 貪欲な量指定子と非貪欲な量指定子

量指定子 *+?{m,n} はデフォルトで 貪欲 であり、パターンの残りが依然として許す限り多くの文字に一致します。多くの場合それがまさに望ましい動作ですが、そうでないこともあります:

>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'

貪欲な .+ は最後の > まで全部を取り込んでしまいました。? を付け加えると量指定子は 非貪欲 になり、可能な限り少なく一致します:

>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'

非貪欲な形式は最初の > で停止します。非貪欲な量指定子は、文字列から対になった区切り文字を抽出する際に絶えず登場します。

2.36.6. 置換における後方参照

re.sub() は、置換文字列の中で \1\2、... を使ってキャプチャされたグループを参照できます。置換ではキャプチャした部分を使ってすべての一致を書き換えます:

>>> re.sub(r'(\d+)\.(\d+)', r'\2.\1', 'swap 12.34 and 5.6')
'swap 34.12 and 6.5'

各一致は 2 つの数値をキャプチャし、置換ではそれらを入れ替えます。\g<1> は同じことを行う別の構文で、置換における次の文字が数字である場合に便利です(r'\g<1>0' は「グループ 10」と読まれるのではなく、グループ 1 にリテラルのゼロを付加します)。

2.36.7. 利用できないもの

CPython のパターンがここに持ち込まれて驚くことのないよう、MicroPython の reサポートしていない ものを念のため挙げておきます:

  • 先読み (?=...) と後読み (?<=...) -- 実装されていません。

  • 名前付きグループ (?P<name>...) と名前付き後方参照 (?P=name) -- 実装されていません。

  • re.IGNORECASEre.MULTILINEre.DOTALL のようなフラグ定数 -- 適用されません。大文字小文字を区別しない集合を作るか、自分で入力を事前に分割してください。

  • match.groups()match.span()match.start()match.end() の各メソッドは、出荷されている OpenMV ボードがどれも有効にしていない ROM レベルでゲートされています。これらに依存するコードはカメラ上で動作しません。

パターン、グループ、アンカーがあれば、カメラ上の正規表現ツールセットは一度に学べるほど小さく、かつ文脈依存の解析を除けば何でもこなせるほど豊富です。