2.35. Conceitos básicos de padrões¶
Uma expressão regular é uma pequena linguagem para descrever strings pela forma, em vez de pelo seu conteúdo exato. Use-a quando uma string corresponde se-e-somente-se ela segue um padrão que você consegue descrever, mas não consegue 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 arquivo, em qualquer ordem, com prefixos v opcionais”.
Recorra a re apenas quando um método simples de string não der conta.
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 desses é mais rápido, mais fácil de ler e mais difícil de errar do que a regex equivalente. Use regex quando a forma da string importa e a substring exata não.
2.35.1. As quatro coisas que você vai usar¶
O módulo re do MicroPython expõe quatro coisas:
re.compile()– transforma uma string de padrão em um objeto de padrão compilado que você pode reutilizar.re.match()– tenta o padrão no início de uma string. O padrão é ancorado na posição 0.re.search()– tenta o padrão em qualquer lugar de uma string. Retorna a primeira correspondência.re.sub()– encontra cada correspondência e a substitui.
Omissões notáveis em relação ao CPython: sem re.findall, sem re.finditer, sem re.split em nível de módulo (padrões compilados têm um método split em vez disso), sem re.fullmatch, sem constantes de flag como re.IGNORECASE. Onde você recorreria a uma dessas no CPython, construa o equivalente a partir de re.search() em um laço.
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 observar:
O padrão é escrito como uma raw string (
r'...') para que a barra invertida em\dchegue aoreem vez de ser processada como um escape de string do Python. Sempre use raw strings para padrões de regex.re.search()retorna um objeto de correspondência em caso de sucesso eNoneem caso de falha. Sempre verifique 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 contém parênteses de captura.
O mesmo padrão com re.match() retorna 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¶
A maioria dos padrões úteis é construída a partir de um pequeno conjunto de partes. As que funcionam no MicroPython:
Caracteres literais – qualquer caractere que não seja especial corresponde a si mesmo. hello corresponde a hello.
Caracteres especiais – . ^ $ * + ? { } [ ] \ | ( ) têm todos os significados abaixo. Para corresponder a um deles literalmente, faça o escape com uma barra invertida: \. corresponde a um ponto literal.
Classes de caracteres – abreviações para conjuntos de caracteres comuns:
\d– qualquer dígito0-9\D– qualquer não dígito\s– qualquer caractere de espaço em branco (espaço, tabulação, quebra de linha)\S– qualquer caractere que não seja espaço em branco\w– qualquer caractere de “palavra”: letras, dígitos, underscore\W– qualquer caractere que não seja de palavra.– qualquer caractere, exceto quebra de 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 e quatro dígitos. \s+ corresponde a um ou mais caracteres de espaço em branco. hello.*world corresponde a hello, qualquer coisa (inclusive nada) e então world.
Nota
Ganancioso significa que o quantificador consome a maior parte possível da entrada, contanto que o restante do padrão ainda corresponda. Contra hello x world y world, o .* em hello.*world corresponde à maior sequência que ainda deixa um world no fim – ele captura x world y, e não o mais curto x. O mesmo vale para + e para a forma de intervalo {m,n}: o motor pega a maior correspondência que conseguir e só recua se o restante do padrão falhar.
2.35.4. Substituição¶
re.sub() encontra cada correspondência e a substitui por uma string. A substituição pode referenciar grupos capturados por meio de \1, \2, … (abordado mais adiante junto com o restante da sintaxe de grupos). Sem grupos, re.sub é uma busca-e-substituição direta sobre uma regex:
>>> 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 sobre a qual operar; o resultado é uma nova string com cada correspondência substituída.
2.35.5. Divisão – apenas em um padrão compilado¶
Não há re.split em nível de módulo. Para dividir com base em uma regex, compile o padrão primeiro e chame 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. Compilando para reutilização¶
Se o mesmo padrão for executado muitas vezes – dentro de um laço ou em uma função crítica de desempenho – 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() em um objeto compilado é o mesmo que as funções em nível de módulo, mas evita o custo da recompilação a cada chamada.
2.35.7. Padrões que não correspondem a nada¶
Três padrões em particular pegam os desenvolvedores de surpresa:
.*corresponde à string vazia.re.search(r'.*', s).group(0)retorna''para qualquer entrada.Um padrão com um caractere especial sem escape é um erro de sintaxe.
re.compile(r'cost: $5')levantaValueErrorporque$significa “fim da string”. User'cost: \$5'.O ponto
.não corresponde a uma quebra de linha. Para corresponder através de quebras de linha, escreva o padrão para tratá-las explicitamente com[\s\S]ou forneça uma linha por vez.
Com essas partes, um padrão pode corresponder a quase qualquer fatia de texto de forma fixa. Extrair dados estruturados de volta da correspondência exige grupos de captura.