2.35. Noções básicas de padrões¶
Uma expressão regular é uma pequena linguagem para descrever strings pela forma em vez do seu conteúdo exato. Use-a quando uma string corresponde se e só se seguir um padrão que pode descrever mas não enumerar – «uma sequência de dígitos seguida de uma unidade», «uma linha que começa com ERROR e termina com um número», «qualquer uma destas extensões de ficheiro, em qualquer ordem, com prefixos v opcionais».
Recorra a re apenas quando um método de string simples não chegar.
str.startswith(),str.endswith()– testar um prefixo ou sufixo fixo.in– testar se uma substring fixa está presente.str.split(),str.find(),str.replace()– trabalhar com delimitadores fixos.
Cada um destes é mais rápido, mais fácil de ler e mais difícil de errar do que a expressão regular equivalente. Use expressões regulares quando a forma da string é importante e a substring exata não.
2.35.1. As quatro coisas que vai usar¶
O módulo re do MicroPython expõe quatro coisas:
re.compile()– transformar uma string de padrão num objeto de padrão compilado que pode reutilizar.re.match()– tentar o padrão no início de uma string. O padrão está ancorado na posição 0.re.search()– tentar o padrão em qualquer lugar de uma string. Devolve a primeira correspondência.re.sub()– encontrar cada correspondência e substituí-la.
Omissões notáveis em comparação com o CPython: sem re.findall, sem re.finditer, sem re.split ao nível do módulo (os padrões compilados têm um método split em vez disso), sem re.fullmatch, sem constantes de sinalizadores como re.IGNORECASE. Quando precisasse de uma dessas no CPython, construa o equivalente a partir de re.search() num ciclo.
2.35.2. Um primeiro padrão¶
O padrão r'\d+' corresponde a um ou mais dígitos:
>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'
Algumas coisas a notar:
O padrão é escrito como uma string raw (
r'...') para que a barra invertida em\dchegue aoreem vez de ser processada como um escape de string Python. Use sempre strings raw para padrões de expressões regulares.re.search()devolve um objeto de correspondência em caso de sucesso eNoneem caso de falha. Verifique sempre antes de chamarmatch.group().m.group(0)é o texto completo que o padrão correspondeu. Os grupos 1, 2, … aparecem mais tarde, quando o padrão contiver parênteses de captura.
O mesmo padrão com re.match() devolve None porque a string não começa com um dígito:
>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>
2.35.3. As partes de um padrão¶
Os padrões mais úteis são construídos a partir de um pequeno conjunto de partes. As que funcionam no MicroPython:
Caracteres literais – qualquer carácter que não seja especial corresponde a si próprio. hello corresponde a hello.
Caracteres especiais – . ^ $ * + ? { } [ ] \ | ( ) têm todos significados abaixo. Para corresponder a um deles literalmente, escape-o com uma barra invertida: \. corresponde a um ponto literal.
Classes de caracteres – abreviaturas para conjuntos de caracteres comuns:
\d– qualquer dígito0-9\D– qualquer não-dígito\s– qualquer carácter de espaço em branco (espaço, tabulação, nova linha)\S– qualquer não-espaço em branco\w– qualquer carácter «de palavra»: letras, dígitos, sublinhado\W– qualquer carácter que não seja de palavra.– qualquer carácter exceto nova linha
Quantificadores – quantas vezes a parte anterior deve corresponder:
*– zero ou mais (ganancioso)+– um ou mais (ganancioso)?– zero ou um{n}– exatamente n{m,n}– entre m e n (inclusive)
Combinando: \d{3}-\d{4} corresponde a três dígitos, um traço, quatro dígitos. \s+ corresponde a um ou mais caracteres de espaço em branco. hello.*world corresponde a hello, qualquer coisa (incluindo nada), depois world.
Nota
Ganancioso significa que o quantificador consome o máximo da entrada possível enquanto o resto do padrão ainda corresponde. Contra hello x world y world, o .* em hello.*world corresponde à sequência mais longa que ainda deixa um world no final – captura x world y, não o mais curto x. O mesmo é verdade para + e a forma de intervalo {m,n}: o motor escolhe a correspondência mais longa possível, recuando apenas se o resto do padrão falhar.
2.35.4. Substituição¶
re.sub() encontra cada correspondência e substitui-a por uma string. A substituição pode referenciar grupos capturados através de \1, \2, … (abordado com o resto da sintaxe de grupos mais adiante). Sem grupos, re.sub é uma substituição direta de localização e substituição numa expressão regular:
>>> re.sub(r'\s+', ' ', 'too many spaces')
'too many spaces'
>>> re.sub(r'\d+', 'N', 'log 12, log 345, log 6')
'log N, log N, log N'
O terceiro argumento é a string na qual operar; o resultado é uma nova string com cada correspondência substituída.
2.35.5. Divisão – apenas num padrão compilado¶
Não existe re.split ao nível do módulo. Para dividir com uma expressão regular, compile o padrão primeiro e chame o seu método split
>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c , d')
['a', 'b', 'c', 'd']
O segundo argumento opcional limita o número de divisões:
>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']
2.35.6. Compilar para reutilização¶
Se o mesmo padrão for executado muitas vezes – dentro de um ciclo ou numa função frequentemente chamada – compile-o uma vez e reutilize o objeto compilado:
digit_run = re.compile(r'\d+')
def first_number(line):
m = digit_run.search(line)
return int(m.group(0)) if m else None
Chamar pattern.match() e pattern.search() num objeto compilado é o mesmo que as funções ao nível do módulo, mas evita o custo de recompilação em cada chamada.
2.35.7. Padrões que não correspondem a nada¶
Três padrões em particular surpreendem os programadores:
.*corresponde à string vazia.re.search(r'.*', s).group(0)devolve''em qualquer entrada.Um padrão com um carácter especial não escapado é um erro de sintaxe.
re.compile(r'cost: $5')lançaValueErrorporque$significa «fim da string». User'cost: \$5'.O ponto
.não corresponde a uma nova linha. Para corresponder em novas linhas, escreva o padrão para as tratar explicitamente com[\s\S]ou processe uma linha de cada vez.
Com estas partes, um padrão pode corresponder a quase qualquer fatia de texto de forma fixa. Extrair dados estruturados da correspondência requer grupos de captura.