2.36. Grupos e âncoras¶
Um padrão pode fazer mais do que dizer “esta string corresponde” – ele pode separar os trechos correspondidos e entregar cada um à aplicação por nome. Parênteses ao redor de parte de um padrão o tornam um grupo de captura; o objeto de correspondência então expõe cada grupo como uma substring separada.
2.36.1. Grupos de captura¶
Envolva qualquer parte de um padrão em (...) para capturar o que ele 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 inteira.
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 levantaIndexError.
Um padrão comum é “corresponder a uma estrutura conhecida e capturar as partes variáveis como inteiros”:
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 subexpressão para que um quantificador possa se aplicar ao grupo inteiro. Esse é o único propósito do agrupamento em r'(ab)+' – “um ou mais ab“. O fato de ab aparecer como grupo 1 é um efeito colateral.
Para agrupar sem capturar, use (?:...)
>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'
Grupos sem captura mantêm os números dos grupos organizados quando um padrão usa agrupamento por estrutura, mas não se importa em extrair cada trecho.
2.36.3. Âncoras¶
Âncoras não correspondem a um caractere – elas correspondem a uma posição.
^– início da string.$– fim da string.
As âncoras são o que faz re.match() e re.search() se comportarem 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 então faz 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 sempre significam o início e o fim da string inteira passada para re.search(). Não há flag re.MULTILINE para fazê-los corresponder a cada quebra de linha embutida, e $ também não corresponde à posição antes de um \n final – ele tem que ser o fim absoluto da entrada. Para obter comportamento por linha, divida a entrada nas quebras de linha primeiro e execute o padrão em cada linha.
2.36.4. Conjuntos de caracteres¶
Colchetes definem um conjunto explícito de caracteres. A correspondência consome exatamente um caractere do conjunto.
[abc]– um dentrea,b,c.[a-z]– um caractere no intervaloa-z(inclusive).[a-zA-Z0-9]– letras ou dígitos. Três intervalos combinados.[^abc]– não um dentrea,b,c. O^só nega quando é o primeiro caractere dentro dos colchetes.
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 retorna None na prática porque o texto literal está em minúsculas. O re do MicroPython não possui flag re.IGNORECASE – para corresponder sem diferenciar maiúsculas de minúsculas, escreva ambas as formas dentro do conjunto:
>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'
Os atalhos de classe (\d, \s, \w e 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 padrão – eles correspondem a tantos caracteres quanto o restante do padrão ainda permitir. Frequentemente é exatamente isso que se deseja; às vezes não é:
>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'
O .+ ganancioso capturou tudo até o último >. Acrescentar ? torna o quantificador preguiçoso – ele corresponde ao mínimo possível:
>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'
A forma preguiçosa para no primeiro >. Quantificadores preguiçosos surgem constantemente ao extrair delimitadores balanceados de uma string.
2.36.6. Retrorreferências na substituição¶
re.sub() pode referenciar grupos capturados na string de substituição por meio de \1, \2, … A substituição reescreve cada correspondência usando os trechos capturados:
>>> 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 os troca. \g<1> é uma sintaxe alternativa para a mesma coisa – útil quando o próximo caractere 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 venha parar aqui e te surpreenda:
Lookahead
(?=...)e lookbehind(?<=...)– não implementados.Grupos nomeados
(?P<name>...)e retrorreferências nomeadas(?P=name)– não implementados.Constantes de flag como
re.IGNORECASE,re.MULTILINE,re.DOTALL– não respeitadas. Construa você mesmo o conjunto que não diferencia maiúsculas de minúsculas ou divida a entrada previamente.Os métodos
match.groups(),match.span(),match.start()ematch.end()dependem de um nível de ROM que nenhuma placa OpenMV distribuída habilita. Código que dependa deles não será executado na cam.
Com padrões, grupos e âncoras, o conjunto de ferramentas de regex na cam é pequeno o suficiente para ser aprendido de uma só vez e rico o suficiente para fazer tudo, exceto análise sensível ao contexto.