As expressões regulares são uma linguagem poderosa para corresponder padrões de texto. Esta página fornece uma introdução básica às expressões regulares, o suficiente para nossos exercícios em Python, e mostra como as expressões regulares funcionam em Python. O módulo "re" do Python é compatível com expressões regulares.
Em Python, uma pesquisa por expressão regular geralmente é escrita da seguinte forma:
match = re.search(pat, str)
O método re.search() usa um padrão de expressão regular e uma string e procura esse padrão dentro da string. Se a pesquisa for bem-sucedida, search() retornará um objeto de correspondência ou None, caso contrário. Portanto, a pesquisa geralmente é seguida imediatamente por uma instrução "if" para testar se a pesquisa foi bem-sucedida, como mostrado no exemplo a seguir, que procura pelo padrão "word:" seguido por uma palavra de três letras (detalhes abaixo):
import re str = 'an example word:cat!!' match = re.search(r'word:\w\w\w', str) # If-statement after search() tests if it succeeded if match: print('found', match.group()) ## 'found word:cat' else: print('did not find')
O código match = re.search(pat, str)
armazena o resultado da pesquisa em uma variável chamada "match". A instrução if testa a correspondência. Se for verdadeira, a pesquisa terá êxito e match.group() for o texto correspondente (por exemplo, "word:cat"). Caso contrário, se a correspondência for "false" (nenhum para ser mais específico), a pesquisa não terá êxito e não haverá texto correspondente.
O "r" no início da string de padrão designa uma string "bruta" do Python que passa por barras invertidas sem alteração, o que é muito útil para expressões regulares (o Java precisa muito desse recurso!). Eu recomendo que você sempre escreva strings de padrão com o "r" como sempre.
Padrões básicos
O poder das expressões regulares é que elas podem especificar padrões, não apenas caracteres fixos. Estes são os padrões mais básicos que correspondem a caracteres únicos:
- a, X, 9, < -- caracteres comuns correspondem exatamente a si mesmos. Os metacaracteres que não se correspondem porque têm significados especiais são: . ^ $ * + ? { [ ] \ | ( ) (detalhes abaixo)
- . (um ponto) -- corresponde a qualquer caractere único, exceto à nova linha "\n"
- \w -- (w minúsculo) corresponde a um caractere de "palavra": uma letra, um dígito ou uma barra inferior [a-zA-Z0-9_]. Observe que, embora "palavra" seja a expressão mnemônica para isso, ela corresponde a apenas um caractere de palavra, não a uma palavra inteira. \W (W maiúsculo) corresponde a qualquer caractere que não seja uma palavra.
- \b -- limite entre palavra e não palavra
- \s -- (s minúsculo) corresponde a um único caractere de espaço em branco -- espaço, nova linha, retorno, tabulação, formulário [ \n\r\t\f]. \S (S maiúsculo) corresponde a qualquer caractere que não seja um espaço.
- \t, \n, \r -- tab, nova linha, retornar
- \d -- dígito decimal [0-9] (alguns utilitários de regex mais antigos não são compatíveis com \d, mas todos são compatíveis com \w e \s)
- ^ = início, $ = fim -- corresponde ao início ou ao fim da string
- \ -- inibem a "especialidade" de um caractere. Por exemplo, use \. para corresponder a um ponto ou \\ para corresponder a uma barra. Se você não tiver certeza se um caractere tem um significado especial, como "@", tente colocar uma barra na frente dele, \@. Se não for uma sequência de escape válida, como \c, o programa Python vai ser interrompido com um erro.
Exemplos básicos
Piada: como é que se chama porco com três olhos? piii!
As regras básicas da pesquisa por expressão regular para um padrão dentro de uma string são:
- A pesquisa segue a string do início ao fim, parando na primeira correspondência encontrada
- Todos os padrões precisam ter correspondência, mas não todas as strings
- Se
match = re.search(pat, str)
for bem-sucedida, a correspondência não será "None" e, em especial, match.group() será o texto correspondente.
## Search for pattern 'iii' in string 'piiig'. ## All of the pattern must match, but it may appear anywhere. ## On success, match.group() is matched text. match = re.search(r'iii', 'piiig') # found, match.group() == "iii" match = re.search(r'igs', 'piiig') # not found, match == None ## . = any char but \n match = re.search(r'..g', 'piiig') # found, match.group() == "iig" ## \d = digit char, \w = word char match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123" match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"
Repetição
Tudo fica mais interessante quando você usa "+" e "*" para especificar repetição no padrão.
- + -- 1 ou mais ocorrências do padrão à esquerda, por exemplo, 'i+' = um ou mais i's
- * -- 0 ou mais ocorrências do padrão à esquerda
- ? -- corresponde a 0 ou 1 ocorrência do padrão à esquerda
Maior e mais à esquerda
Primeiro, a pesquisa encontra a correspondência mais à esquerda para o padrão e, depois, tenta usar o máximo possível da string, ou seja, usar + e * ir o mais longe possível (dizem que + e * são "gananciosos").
Exemplos de repetição
## i+ = one or more i's, as many as possible. match = re.search(r'pi+', 'piiig') # found, match.group() == "piii" ## Finds the first/leftmost solution, and within it drives the + ## as far as possible (aka 'leftmost and largest'). ## In this example, note that it does not get to the second set of i's. match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii" ## \s* = zero or more whitespace chars ## Here look for 3 digits, possibly separated by whitespace. match = re.search(r'\d\s*\d\s*\d', 'xx1 2 3xx') # found, match.group() == "1 2 3" match = re.search(r'\d\s*\d\s*\d', 'xx12 3xx') # found, match.group() == "12 3" match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123" ## ^ = matches the start of string, so this fails: match = re.search(r'^b\w+', 'foobar') # not found, match == None ## but without the ^ it succeeds: match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"
Exemplo de e-mails
Suponha que você queira encontrar o endereço de e-mail dentro da string "xyz alice-b@google.com macaco roxo". Ele será usado como um exemplo para demonstrar mais atributos de expressão regular. Confira uma tentativa usando o padrão r'\w+@\w+':
str = 'purple alice-b@google.com monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print(match.group()) ## 'b@google'
Nesse caso, a pesquisa não obtém o endereço de e-mail inteiro, porque \w não corresponde a "-" ou "." no endereço. Vamos corrigir isso usando os recursos de expressão regular abaixo.
Parênteses
Colchetes podem ser usados para indicar um conjunto de caracteres, portanto [abc] corresponde a "a", "b" ou "c". Os códigos \w, \s etc. também funcionam dentro de colchetes, com a única exceção que ponto (.) significa apenas um ponto literal. Para o problema de e-mails, os colchetes são uma maneira fácil de adicionar "." e "-" ao conjunto de caracteres que podem aparecer em torno do @ com o padrão r'[\w.-]+@[\w.-]+' para obter o endereço de e-mail completo:
match = re.search(r'[\w.-]+@[\w.-]+', str) if match: print(match.group()) ## 'alice-b@google.com'(Mais recursos de colchete) Você também pode usar um traço para indicar um intervalo, de modo que [a-z] corresponda a todas as letras minúsculas. Para usar um traço sem indicar um intervalo, insira o traço por último, por exemplo, [abc-]. Um chapéu (^) no início de um colchete o inverte, portanto, [^ab] significa qualquer caractere, exceto "a" ou "b".
Extração de grupo
O recurso "grupo" de uma expressão regular permite que você selecione partes do texto correspondente. Suponha que, para o problema de e-mails, queremos extrair o nome de usuário e o host separadamente. Para isso, adicione parênteses ( ) ao nome do usuário e ao host no padrão, desta forma: r'([\w.-]+)@([\w.-]+)'. Nesse caso, os parênteses não alteram a correspondência do padrão, mas estabelecem "grupos" lógicos dentro do texto de correspondência. Em uma pesquisa bem-sucedida, match.group(1) é o texto de correspondência correspondente ao primeiro parêntese esquerdo e match.group(2) é o texto que corresponde ao segundo parêntese esquerdo. O match.group() simples ainda é o texto de correspondência inteiro, como de costume.
str = 'purple alice-b@google.com monkey dishwasher' match = re.search(r'([\w.-]+)@([\w.-]+)', str) if match: print(match.group()) ## 'alice-b@google.com' (the whole match) print(match.group(1)) ## 'alice-b' (the username, group 1) print(match.group(2)) ## 'google.com' (the host, group 2)
Um fluxo de trabalho comum com expressões regulares é que você escreve um padrão para o que está procurando, adicionando grupos de parênteses para extrair as partes desejadas.
Findall
findall() é provavelmente a única função mais poderosa no módulo re. Acima, usamos re.search() para encontrar a primeira correspondência de um padrão. findall() encontra *todas* as correspondências e as retorna como uma lista de strings, em que cada string representa uma correspondência.## Suppose we have a text with many email addresses str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher' ## Here re.findall() returns a list of all the found email strings emails = re.findall(r'[\w\.-]+@[\w\.-]+', str) ## ['alice@google.com', 'bob@abc.com'] for email in emails: # do something with each found email string print(email)
findall com arquivos
Para arquivos, é possível que você tenha o hábito de escrever uma repetição para iterar nas linhas do arquivo e, depois, chamar findall() em cada linha. Em vez disso, deixe findall() fazer a iteração por você - muito melhor! Basta inserir o texto inteiro do arquivo em findall() e deixar que ele retorne uma lista de todas as correspondências em uma única etapa (lembre-se de que f.read() retorna todo o texto de um arquivo em uma única string):
# Open file f = open('test.txt', encoding='utf-8') # Feed the file text into findall(); it returns a list of all the found strings strings = re.findall(r'some pattern', f.read())
findall e Groups
O mecanismo de grupo de parênteses ( ) pode ser combinado com findall(). Se o padrão incluir dois ou mais grupos de parênteses, em vez de retornar uma lista de strings, findall() retornará uma lista de *tuples*. Cada tupla representa uma correspondência do padrão e, dentro dela, estão os dados group(1), group(2) .. Portanto, se dois grupos de parênteses forem adicionados ao padrão de e-mail, findall() retornará uma lista de tuplas, cada tamanho 2 contendo o nome de usuário e o host, por exemplo, ("alice", "google.com").
str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher' tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str) print(tuples) ## [('alice', 'google.com'), ('bob', 'abc.com')] for tuple in tuples: print(tuple[0]) ## username print(tuple[1]) ## host
Depois de ter a lista de tuplas, é possível repetir sobre ela para fazer algum cálculo para cada tupla. Se o padrão não incluir parênteses, findall() retornará uma lista de strings encontradas, como nos exemplos anteriores. Se o padrão incluir um único conjunto de parênteses, findall() retornará uma lista de strings correspondentes a esse único grupo. (Recurso opcional obscuro: às vezes existem agrupamentos de parêntese ( ) no padrão, mas os quais você não deseja extrair. Nesse caso, escreva os parênteses com um ?: no início, por exemplo, (?: ), e esse parêntese esquerdo não contará como um resultado de grupo.)
Fluxo de trabalho e depuração RE
Os padrões de expressão regular contêm muito significado em apenas alguns caracteres , mas eles são tão densos que você pode gastar muito tempo depurando seus padrões. Configure seu tempo de execução para que você possa executar um padrão e imprimir o que corresponde facilmente, por exemplo, executando-o em um texto pequeno de teste e gerando o resultado de findall(). Se o padrão não corresponder a nada, tente enfraquecer o padrão, removendo partes dele para que você receba muitas correspondências. Quando não há correspondência, não é possível progredir, porque não há nada concreto a ser analisado. Quando há muitas correspondências, você pode melhorar cada vez mais para alcançar exatamente o que quer.
Opções
As funções re têm opções para modificar o comportamento da correspondência de padrão. A sinalização de opção é adicionada como um argumento extra a search() ou findall() etc., por exemplo, re.search(pat, str, re.IGNORECASE).
- IGNORECASE -- ignora as diferenças em maiúsculas/minúsculas para a correspondência, portanto "a" corresponde a "a" e "A".
- DOTALL -- permite que o ponto (.) corresponda a nova linha. Normalmente, ele corresponde a tudo, exceto a nova linha. Isso pode confundir você. Você acha que .* corresponde a tudo, mas, por padrão, não passa do fim de uma linha. Observe que \s (espaço em branco) inclui novas linhas. Portanto, se você quiser corresponder a uma sequência de espaços em branco que possa incluir uma nova linha, basta usar \s*
- MULTILINE -- Dentro de uma string formada por muitas linhas, permita que ^ e $ correspondam ao início e ao fim de cada linha. Normalmente, ^/$ corresponde ao início e ao fim de toda a string.
Greedy x não grisalho (opcional)
Esta é uma seção opcional que mostra uma técnica de expressão regular mais avançada que não é necessária para os exercícios.
Digamos que haja um texto com tags: <b>foo</b> e <i>assim por diante</i>
Suponha que você esteja tentando fazer a correspondência de cada tag com o padrão '(<.*>)'. Qual é a primeira correspondência?
O resultado é um pouco surpreendente, mas o aspecto ganancioso de .* faz com que ele corresponda a "<b>foo</b> inteiro" e <i>assim por diante</i>" como uma grande correspondência. O problema é que o .* vai o mais longe possível, em vez de parar no primeiro > (ou seja, é "ganancioso").
Há uma extensão para a expressão regular onde você adiciona um ? no final, como .*? ou .+?, mudando-os para não gananciosos. Agora, eles param o mais rápido possível. Assim, o padrão "(<.*?>)" receberá apenas "<b>" como a primeira correspondência, e "</b>" como a segunda correspondência, e assim por diante, obtendo cada par <..> por vez. O estilo normalmente é usar um .*? imediatamente seguido por um marcador concreto (>, neste caso) para o qual a execução de .*? é forçada.
A extensão *? se originou em Perl, e expressões regulares que incluem extensões de Perl são conhecidas como expressões regulares compatíveis com Perl -- pcre. O Python inclui suporte a PC. Muitos utilitários de linha de comando etc. têm um sinalizador onde aceitam padrões de PC.
Uma técnica mais antiga, mas amplamente utilizada, para codificar a ideia de "todos esses caracteres, exceto parar em X", usa o estilo de colchete. Para o exemplo acima, você poderia escrever o padrão, mas, em vez de .* para obter todos os caracteres, use [^>]* que pula todos os caracteres que não são > (o ^ inicial "inverte" o conjunto de colchetes, para que corresponda a qualquer caractere que não esteja entre colchetes).
Substituição (opcional)
A função re.sub(pat, replace, str) procura todas as instâncias de padrão na string fornecida e as substitui. A string de substituição pode incluir "\1", "\2", que se referem ao texto do grupo(1), do grupo(2) e assim por diante a partir do texto correspondente original.
Este é um exemplo que pesquisa todos os endereços de e-mail e os altera para manter o usuário (\1), mas com yo-yo-dyne.com como host.
str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher' ## re.sub(pat, replacement, str) -- returns new string with all replacements, ## \1 is group(1), \2 group(2) in the replacement print(re.sub(r'([\w\.-]+)@([\w\.-]+)', r'\1@yo-yo-dyne.com', str)) ## purple alice@yo-yo-dyne.com, blah monkey bob@yo-yo-dyne.com blah dishwasher
Exercício
Para praticar expressões regulares, consulte o Exercício de nomes de bebês.