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çaIndexError.
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 dea,b,c.[a-z]– um carácter no intervaloa-z(inclusive).[a-zA-Z0-9]– letras ou dígitos. Três intervalos combinados.[^abc]– não um dea,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(), ematch.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.