2.36. Grupos e âncoras

Um padrão pode fazer mais do que dizer «esta string corresponde» – pode separar as partes correspondentes e entregar cada uma à aplicação pelo nome. Parênteses em torno de parte de um padrão tornam-no num grupo de captura; o objeto de correspondência expõe então cada grupo como uma substring separada.

2.36.1. Grupos de captura

Envolva qualquer parte de um padrão em (...) para capturar o que correspondeu:

>>> 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'
  • O grupo 0 é sempre a correspondência completa.

  • Os grupos 1, 2, … são as substrings capturadas, numeradas da esquerda para a direita pelo parêntese de abertura.

  • Chamar match.group() com um índice além do último grupo lança IndexError.

Um padrão comum é «corresponder a uma estrutura conhecida, capturar as partes variáveis como ints»:

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. Grupos sem captura

Os parênteses também agrupam uma sub-expressão para que um quantificador possa ser aplicado ao grupo inteiro. Esse é o único propósito do agrupamento em r'(ab)+' – «um ou mais de ab«. O facto de ab aparecer como grupo 1 é um efeito secundário.

Para agrupar sem capturar, use (?:...)

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

Os grupos sem captura mantêm os números de grupo organizados quando um padrão usa agrupamento para estrutura, mas não precisa de extrair cada parte.

2.36.3. Âncoras

As âncoras não correspondem a um carácter – correspondem a uma posição.

  • ^ – início da string.

  • $ – fim da string.

As âncoras são o que faz re.match() e re.search() comportarem-se de forma diferente. re.match(p, s) é o mesmo que re.search('^' + p, s): força o padrão a começar na posição 0. Adicionar $ ao fim de um padrão faz então com que o padrão corresponda à string inteira e a nada mais:

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

^ e $ no re do MicroPython significam sempre o início e o fim da string completa passada a re.search(). Não existe o sinalizador re.MULTILINE para os fazer corresponder a cada nova linha incorporada, e $ também não corresponde à posição antes de um \n final – tem de ser o fim absoluto da entrada. Para obter comportamento por linha, divida a entrada nas novas linhas primeiro e execute o padrão em cada linha.

2.36.4. Conjuntos de caracteres

Os parênteses retos definem um conjunto explícito de caracteres. A correspondência consome exatamente um carácter do conjunto.

  • [abc] – um de a, b, c.

  • [a-z] – um carácter no intervalo a-z (inclusive).

  • [a-zA-Z0-9] – letras ou dígitos. Três intervalos combinados.

  • [^abc]não um de a, b, c. O ^ só nega quando é o primeiro carácter dentro dos parênteses retos.

Exemplos:

>>> 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

A primeira chamada devolve None na prática porque o texto literal está em minúsculas. O re do MicroPython não tem o sinalizador re.IGNORECASE – para corresponder sem distinção entre maiúsculas e minúsculas, escreva ambos os casos no conjunto:

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

Os atalhos de classe (\d, \s, \w, e as suas formas negadas) também podem ser usados dentro de [...]: [\w-] é «caracteres de palavra ou um traço literal.»

2.36.5. Quantificadores gananciosos vs preguiçosos

Os quantificadores *, +, ?, e {m,n} são gananciosos por defeito – correspondem ao máximo de caracteres que o resto do padrão ainda permitir. Muitas vezes é exatamente o que se pretende; por vezes não é:

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

O .+ ganancioso avançou até ao último >. Acrescentar ? torna o quantificador preguiçoso – corresponde ao mínimo possível:

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

A forma preguiçosa para na primeira >. Os quantificadores preguiçosos aparecem constantemente quando se extraem delimitadores equilibrados de uma string.

2.36.6. Referências anteriores em substituição

re.sub() pode referir-se aos grupos capturados na string de substituição através de \1, \2, … A substituição reescreve cada correspondência usando as partes capturadas:

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

Cada correspondência captura dois números, e a substituição troca-os. \g<1> é uma sintaxe alternativa para a mesma coisa – útil quando o carácter seguinte na substituição é um dígito (r'\g<1>0' para acrescentar um zero literal ao grupo 1 em vez de ler «grupo 10»).

2.36.7. O que não está disponível

Um lembrete do que o re do MicroPython não suporta, caso um padrão do CPython apareça aqui e surpreenda:

  • Lookahead (?=...) e lookbehind (?<=...) – não implementados.

  • Grupos nomeados (?P<name>...) e referências anteriores nomeadas (?P=name) – não implementados.

  • Constantes de sinalizadores como re.IGNORECASE, re.MULTILINE, re.DOTALL – não suportadas. Construa o conjunto sem distinção de maiúsculas ou divida previamente a entrada.

  • Os métodos match.groups(), match.span(), match.start(), e match.end() estão condicionados a um nível de ROM que nenhuma placa OpenMV comercializada ativa. O código que deles depende não funcionará na câmara.

Com padrões, grupos e âncoras, o conjunto de ferramentas de regex na câmara é suficientemente pequeno para aprender numa única sessão e suficientemente rico para fazer tudo, exceto análise sensível ao contexto.