RegEx – Expressões regulares em Python

regex_destaque

Introdução

Expressões regulares (chamadas também de regex que vem de regular expressions) são uma linguagem que detecta padrões. Você expressa um padrão (pattern em inglês) que será aplicado a strings. É uma forma eficiente de encontrar padrões e extrair informações de textos. Nesse padrão, cada símbolo representa um tipo de informação e, nesse artigo, veremos os principais símbolos e seus significados com o auxílio de diversos exemplos.

O Python possui o módulo re para expressões regulares. A documentação é bem detalhada e ainda há esse texto com uma introdução mais passo a passo do módulo.

O módulo possui três métodos que são muito utilizados e que serão nosso foco aqui: match, search e findall.

Match

O match procura pelo padrão fornecido desde o início da string. A assinatura é re.match(padrão, string, flags=0). O retorno é None quando não encontra o padrão. Quando encontra, retorna um Match object sobre o qual falaremos no decorrer do artigo.

Vamos começar procurando o padrão “Fran” na string “Francisco Bustamante”, autor do site:

>>> import re

>>> re.match('Fran', 'Francisco Bustamante')
<re.Match object; span=(0, 4), match='Fran'>

>>> re.match('Fran', 'Bustamante, Francisco') 

# repare que o último retorna None pois não encontra no início

O span=(0, 4) significa que o padrão foi encontrado entre os índices 0 e 4. Lembre que a contagem em sequências Python começa em zero e que o último índice é excluso.

Search

O search procura pelo padrão fornecido ao longo da string. A assinatura é re.search(padrão, string, flags=0). O retorno é None quando não encontra o padrão. Quando encontra, retorna um Match object sobre o qual falaremos no decorrer do artigo. Importante resaltar que retornará a primeira localização onde o padrão foi encontrado.

>>> re.search('an', 'Francisco Bustamante')
<re.Match object; span=(2, 4), match='an'>

O padrão an aparece na string mais de uma vez, no entanto, o search retorna em span apenas os índices da primeira localização.

Findall

O findall procura pelo padrão fornecido ao longo da string. A assinatura é re.findall(padrão, string, flags=0). O retorno é uma lista vazia quando não encontra o padrão. Quando encontra, retorna uma lista com cada ocorrência. Importante ressaltar que retornará todas as ocorrências encontradas.

>>> re.findall('an', 'Francisco Bustamante')                                     
['an', 'an']

>>> re.findall('an', 'Chico')                                                    
[]

O poder do findall será melhor explorado com algumas ferramentas que aprenderemos no decorrer do artigo.

Sinalizadores

A assinatura dos métodos apresentados possuem um argumento flags, que pode ser traduzido como sinalizadores. Com sinalizadores podemos modificar alguns aspectos de como as expressões regulares funcionam. Veja a diferença de comportamento nos dois exemplos a seguir:

>>> re.match('fran', 'Francisco Bustamante')                                     

>>> re.match('fran', 'Francisco Bustamante', re.IGNORECASE)                      
<re.Match object; span=(0, 4), match='Fran'>

No primeiro caso, o retorno foi None, de forma que o interpretador apenas apresentou uma linha vazia. Isso porque o padrão foi passado com a letra inicial minúscula e, na string, ela se apresenta maiúscula. O sinalizador re.IGNORECASE, como o nome sugere, desconsidera diferenciação entre maiúsculo e minúsculo.

Os seguintes sinalizadores estão disponíveis:

FlagSignificado
ASCII, AConsidera caracteres de escape como \w, \b, \s e \d apenas em caracteres ASCII
DOTALL, SFaz com que o metacaractere . encontre qualquer caractere, incluindo novas linhas
IGNORECASE, IFaz combinações sem diferenciar maiúsculo de minúsculo
LOCALE, LFaz uma correspondência considerando a localidade
MULTILINE, MCorrespondência multilinha, afetando ^ e $
VERBOSE, X (de ‘extended’)Habilita expressões regulares detalhadas, que podem ser organizadas de forma mais clara e compreensível.

As letras após cada nome são as abreviações que podem ser utilizadas no lugar do nome completo.

Apareceram algumas palavras novas nessa tabela. Não se preocupe, elas serão explicadas no decorrer do artigo. Uma das palavras é metacaractere, nosso próximo tópico.

O poder das expressões regulares vem dos metacaracteres, caracteres que representam um conjunto de caracteres específicos, padrões gerais.

O metacaractere ponto (.)

O metacaractere . representa qualquer caractere, exceto quebra de linha. Vamos utilizá-lo com o método match visto anteriormente:

>>> re.match('.', 'Francisco Bustamante') 
<re.Match object; span=(0, 1), match='F'>

>>> re.match('.', '42')
<re.Match object; span=(0, 1), match='4'>

>>> re.match('.', ' Francisco Bustamante')
<re.Match object; span=(0, 1), match=' '>

Repare no último exemplo que havia um espaço no início da string e esse espaço foi reconhecido pelo metacaractere.

De acordo com a definição, o metacaractere . deve considerar caracteres de controle exceto o \n, que indica quebra de linha. Vejamos:

>>> re.match('.', '\t\t')  # \t representa TAB
<re.Match object; span=(0, 1), match='\t'>

>>> re.match('.', '\n')

>>> print(re.match('.', '\n'))
None

Esse comportamento de ignorar \n pode ser modificado por um dos sinalizadores que vimos anteriormente, o DOTALL:

>>> re.match('.', '\n', re.DOTALL)                                              
<re.Match object; span=(0, 1), match='\n'>

Vimos anteriormente que o search busca o padrão no decorrer da string e retorna a primeira posição onde encontra. Vejamos o comportamento com .:

>>> re.search('.', ' Francisco Bustamante')                                     
<re.Match object; span=(0, 1), match=' '>

>>> re.search('.', 'Francisco Bustamante')                                      
<re.Match object; span=(0, 1), match='F'>

>>> re.search('.', '\nFrancisco Bustamante')                                    
<re.Match object; span=(1, 2), match='F'>

>>> re.search('.', '\nFrancisco Bustamante', re.DOTALL)                         
<re.Match object; span=(0, 1), match='\n'>

Os dois primeiros exemplos retornam a primeira posição, conforme esperado. No terceiro exemplo, o caractere de controle \n é ignorado, retornando o primeiro caractere logo após, a letra F. Esse comportamento é modificado pelo sinalizador DOTALL no último exemplo.

Vimos anteriormente que o findall procura pelo padrão fornecido ao longo da string, retornando uma lista com todas as ocorrências. Vamos combiná-lo com .:

>>> re.findall('.', 'Ciência\nProgramada')                                      
['C', 'i', 'ê', 'n', 'c', 'i', 'a', 'P', 'r', 'o', 'g', 'r', 'a', 'm', 'a', 'd', 'a']

O caractere de controle \n foi ignorado e todos os demais foram retornados na forma de lista.

Âncoras

O símbolo ^ representa início de string e o símbolo $ representa final de string. Vamos testar com o método findall:

>>> re.findall('^.', 'Ciência\nProgramada\nPython')                             
['C']

>>> re.findall('^.', 'Ciência\nProgramada\nPython', re.MULTILINE)
['C', 'P', 'P']

O padrão ^. significa procurar por qualquer caractere que não seja uma quebra de linha no início da string. Assim, no primeiro caso, retorna a letra C. Mas repare que a string passada possui quebras de linha. Logo, podemos passar o sinalizador re.MULTILINE para que o padrão seja procurado em cada linha. Por isso o segundo exemplo retorna uma lista com a primeira letra da string e, também, com a primeira letra após o caractere de controle \n.

Podemos aplicar a mesma lógica para o padrão .$, que buscará no final da string:

>>> re.findall('.$', 'Ciência\nProgramada\nPython')                             
['n']

>>> re.findall('.$', 'Ciência\nProgramada\nPython', re.MULTILINE)
['a', 'a', 'n']

Alguns casos limite que podemos analisar é quando temos uma string com apenas um caractere, ou vazia ou com uma quebra de linha:

>>> re.match('^.$', 'a') 
<re.Match object; span=(0, 1), match='a'>

>>> re.match('^$', '')  # o início ser igual ao fim, string vazia
<re.Match object; span=(0, 0), match=''>

>>> re.findall('^$', '\n', re.MULTILINE)
['', '']

O metacaractere . é muito abrangente, usualmente queremos ser um pouco mais específicos.

Classes de caracteres

Quando o padrão apresenta colchetes, estes declaram um classe de caracteres. Irá se buscar cada caractere entre colchetes no texto da string. Vamos procurar por vogais minúsculas na string Ciência Programada:

>>> re.findall('[aeiou]', 'Ciência Programada')                                 
['i', 'i', 'a', 'o', 'a', 'a', 'a']

Observe que o ê não foi colocado na lista, afinal é diferente de e.

O símbolo ^ quando dentro de um classe de caractere significa negação. Logo, se estamos buscando tudo menos vogais minúsculas:

>>> re.findall('[^aeiou]', 'Ciência Programada')
['C', 'ê', 'n', 'c', ' ', 'P', 'r', 'g', 'r', 'm', 'd']

É possível também definir ranges, faixas, de valores. Buscando de “a” até “f”:

>>> re.findall('[a-f]', 'Ciência Programada') 
['c', 'a', 'a', 'a', 'd', 'a']

Podemos definir mais de uma faixa. Buscando de “a” até “f” e de “A” até “Z”:

>>> re.findall('[a-fA-Z]', 'Ciência Programada') 
['C', 'c', 'a', 'P', 'a', 'a', 'd', 'a']

Um padrão bem comum é buscar por todas as letras e dígitos e por “_”, já que costumam ser os caracteres mais aceitos em campos de formulário online como e-mail, por exemplo:

>>> re.findall('[a-zA-Z0-9_]', 'Ciência Programada')
['C', 'i', 'n', 'c', 'i', 'a', 'P', 'r', 'o', 'g', 'r', 'a', 'm', 'a', 'd', 'a']

É uma sequência tão especial que há um atalho, o \w:

>>> re.findall('\w', 'Ciência Programada')
['C', 'i', 'ê', 'n', 'c', 'i', 'a', 'P', 'r', 'o', 'g', 'r', 'a', 'm', 'a', 'd', 'a']

Observe duas pequenas diferenças deste resultado com relação ao exemplo anterior. Primeiro, não é necessário os colchetes para utilizar o \w. Segundo, o caractere ê aparece no resultado. Ou seja, aqui se reconhece caracteres unicode. Caso realmente queira apenas o equivalente à classe [a-zA-Z0-9_], utilize o sinalizador re.ASCII:

>>> re.findall('\w', 'Ciência Programada', re.ASCII)
['C', 'i', 'n', 'c', 'i', 'a', 'P', 'r', 'o', 'g', 'r', 'a', 'm', 'a', 'd', 'a']

As principais sequências especiais definidas por padrão são:

  • \d equivalente a qualquer dígito unicode, o que inclui [0-9]
  • \D equivalente a negação de \d
  • \s equivalente a caracteres de espaço em branco em unicode, o que inclui [ \t\n\r\f\v]
  • \S equivalente a negação de \s
  • \w equivalente a caracteres que podem ser utilizados em textos em geral, o que inclui [a-zA-Z0-9_]
  • \W equivalente a negação de \w

Quando se usa o sinalizador re.ASCII, cada caso anterior fica restrito à representação em colchete apresentada. Em unicode são mais abrangentes.

Raw strings

Todas as sequências especiais utilizam o símbolo \. Isso é problemático em Python, pois strings aceitam caracteres de controle na linguagem. Strings literais avaliam o caractere após a barra invertida para verificar se é o caso de um caractere de controle ou não. Vamos ver um exemplo:

>>> print('1\n2')
1
2

Quando queremos indicar que não é para considerar como um caractere de controle, utilizamos uma outra barra invertida para sinalizar escape:

>>> print('1\\n2')  # escape da segunda barra invertida
1\n2

Esse comportamento relacionado a barras invertidas pode ser muito problemático em alguns contextos. Por exemplo, na área científica se usa muito LaTeX para produção de documentos. E os ambientes em LaTeX são delimitados com comandos que utilizam \. Por exemplo, \begin{equation}...\end{equation} delimita um ambiente para uma equação matemática. O comando \section indica o início de uma seção em um documento. Vamos ver como o interpretador Python reconhece o comando seguido de uma quebra de linha:

>>> '\section\n' # \s não tem significado em Python
'\\section\n'
>>> text = '\section\n'  # LaTeX
>>> print(text)
\section

Repare que, como \s não tem significado para o interpretador Python, automaticamente ele adiciona uma barra invertida para sinalizar que deve se ignorar tal caractere. Ao usar print verificamos que aparece normalmente o texto do comando e uma linha em branco.

Podemos verificar que caracteres de controle são considerados como um único caractere verificando o tamanho da string armazenada na variável text:

>>> len(text)
9

Temos 8 caracteres em “\section” e o nono é o caractere de controle \n.

Teoricamente, deveria haver match ao se buscar por \\section na string armazenada em text:

>>> print(re.match('\\section', text))  # \s é uma sequência especial em regex
None

Mas, como vimos na seção anterior, \s possui significado dentro do contexto do módulo re. Assim, precisamos indicar que é para dar escape nas barras invertidas:

>>> print(re.match('\\\\section', text))
<re.Match object; span=(0, 8), match='\\section'>

Bem-vindo ao que chamamos de a praga da barra invertida! Mas calma, há como evitar esse monte de barras. Em Python, há o que chamamos de strings cruas ou raw strings do inglês. Nesse tipo de strings, denotadas por um r antes das aspas, as barras invertidas não são tratadas de nenhuma forma especial. Perceba a diferença:

>>> len('\n')                                                                   
1

>>> len(r'\n')                                                                  
2

Na raw string, temos dois caracteres, o “\” e o “n”, enquanto que na string normal (literal), há apenas um caractere que representa quebra de linha.

Voltando ao nosso exemplo, basta utilizar raw string no padrão:

>>> print(re.match(r'\\section', text)) # raw string, significa que não há caractere de controle
<re.Match object; span=(0, 8), match='\\section'>

Assim, uma dica muito importante: utilize raw strings quando houver barra invertida que não deve ser interpretada como sequência especial.

Uso de pipe

O pipe | significa ou indicando alternativas no uso da expressão regular. Veja exemplos:

>>> re.search('a|b', 'abc')
<re.Match object; span=(0, 1), match='a'>

>>> re.search('a|b', 'bcd')
<re.Match object; span=(0, 1), match='b'>

>>> re.search('a|b', 'cde')

Observe no primeiro exemplo que, mesmo existindo “b” na string, como o “a” foi encontrado primeiro apenas a posição de “a” foi retornada. Com findall, ambos são retornados:

>>> re.findall('b|a', 'abc')
['a', 'b']

Repetições

Buscar repetições é um dos motivos mais comuns que levam ao uso de expressões regulares. Vamos verificar as diversas formas possíveis.

Quantidades especificas

Vamos verificar o comportamento da busca pelo padrão \d{4} que busca qualquer dígito quatro vezes.

>>> re.match(r'\d{4}', '1234')                 # string com 4 dígitos
<re.Match object; span=(0, 4), match='1234'>

>>> re.match(r'\d{4}', '123')                  # string com 3 dígitos

>>> re.match(r'\d{4}', '12345')                # string com 5 dígitos
<re.Match object; span=(0, 4), match='1234'>

Nos exemplos, fica claro que, quando há menos de quatro dígitos, o retorno é None. Nos demais casos, retorna os quatro primeiros dígitos. O mesmo ocorre com o uso do método search:

>>> re.search(r'\d{4}', 'abc123def12345')                                       
<re.Match object; span=(9, 13), match='1234'>

O primeiro seguimento 123 foi ignorado por ter menos de 4 dígitos. O segundo seguimento, que possui 5 dígitos, foi considerado e retornou os quatro primeiros dígitos.

Quantidade mínima e máxima

O uso de vírgula indica que o valor a ser procurado é o mínimo. Ou seja, se houver mais dígitos o comportamento será ganancioso (tradução usual de greed, sendo também comum a tradução “guloso”) e retornará os demais dígitos além do mínimo. Havendo menos dígitos que o mínimo, o retorno será None.

>>> re.match(r'\d{2,}', '12')
<re.Match object; span=(0, 2), match='12'>

>>> re.match(r'\d{2,}', '12345')  # guloso ou ganancioso (greed)
<re.Match object; span=(0, 5), match='12345'>

>>> re.match(r'\d{2,}', '1')

Utilizar o símbolo ? após as chaves transforma o comportamento em preguiçoso, sendo portanto tal símbolo um modificador de repetição.

>>> re.match(r'\d{2,}?', '12345')  # preguiçoso, mínimo possível
<re.Match object; span=(0, 2), match='12'>

Um valor após a vírgula, indica valor máximo. As demais explicações anteriores seguem válidas.

>>> re.match(r'\d{2,4}', '12345')
<re.Match object; span=(0, 4), match='1234'>

>>> re.match(r'\d{2,4}', '123')
<re.Match object; span=(0, 3), match='123'>

>>> re.match(r'\d{2,4}', '12')
<re.Match object; span=(0, 2), match='12'>

>>> re.match(r'\d{2,4}', '1')

>>> re.match(r'\d{2,4}?', '12345')  # ganancioso para preguiçoso
<re.Match object; span=(0, 2), match='12'>

0 ou 1 ocorrencia, elemento opcional

A busca por um elemento opcional na string é, na realidade, um caso especial de quantidade mínima e máxima, com mínimo de zero e máximo de um.

>>> re.match(r'\d{0,1}', '12345')
<re.Match object; span=(0, 1), match='1'>

>>> re.match(r'\d{,1}', '12345')  # 0 pode ser omitido
<re.Match object; span=(0, 1), match='1'>

O símbolo ? após uma expressão regular tem o mesmo efeito que {,1}. Logo:

>>> re.match(r'\d?', '12345') 
<re.Match object; span=(0, 1), match='1'>

Mas já vimos que o mesmo símbolo transforma a busca de gananciosa em preguiçosa. Assim, a seguinte expressão retorna uma string vazia, pois o mínimo é de nenhuma (0) ocorrência:

>>> re.match(r'\d??', '12345') 
<re.Match object; span=(0, 0), match=''>

Vamos por partes. O primeiro sinal de interrogação é um modificador de repetição da expressão regular imediatamente anterior, buscando 0 ou 1 ocorrência de \d. O segundo sinal de interrogação é um modificador do operador de repetição, transformando-o em preguiçoso.

0 ou mais vezes

Outro caso especial de mínimo e máximo. Também possui um símbolo especial, o *. Observe os exemplos:

>>> re.match(r'\d{0,}', '12345')
<re.Match object; span=(0, 5), match='12345'>

>>> re.match(r'\d{,}', '12345')  # 0 pode ser omitido
<re.Match object; span=(0, 5), match='12345'>

>>> re.match(r'\d*', '12345')  # símbolo
<re.Match object; span=(0, 5), match='12345'>

>>> re.match(r'\d*?', '12345')  # preguiçoso
<re.Match object; span=(0, 0), match=''>

>>> re.match(r'\d*', 'abc')
<re.Match object; span=(0, 0), match=''>

No último exemplo, retorna uma string vazia no match, pois o mínimo é de nenhuma ocorrência.

1 ou mais vezes

Outro caso especial de mínimo e máximo. Também possui um símbolo especial, o +. Observe os exemplos:

>>> re.match(r'\d{1,}', '12345')
<re.Match object; span=(0, 5), match='12345'>

>>> re.match(r'\d+', '12345')  # + exige no mínimo uma ocorrência, sendo ganancioso
<re.Match object; span=(0, 5), match='12345'>

>>> re.match(r'\d+?', '12345')  # transforma em preguiçoso
<re.Match object; span=(0, 1), match='1'>

>>> re.match(r'\d+', 'abc')

No último exemplo, retorna None, pois o mínimo é de uma ocorrência e não há dígitos na string.

Entendendo a importância de controle de repetições

Caso tudo tenha parecido muito abstrato até o momento, vamos começar a colocar algumas situações mais próximas ao real. Considere a string abaixo da qual gostaríamos de extrair todos os valores dos atributos.

>>> text = 'nome="Francisco" site="Ciência Programada"'

Como os valores estão entre aspas duplas, poderíamos imaginar num primeiro momento o padrão r'".+"', já que . pegaria os caracteres com o modificador + indicando uma ou mais vezes. Mas veja o resultado:

>>> re.findall(r'".+"', text)                                                   
['"Francisco" site="Ciência Programada"']

Não funcionou pois colocamos qualquer coisa que está entre aspas com pelo menos uma ocorrência. A busca se estende da primeira aspa dupla até a última. Na realidade, queremos o que está entre cada par de aspas. Logo, devemos transformar a busca em preguiçosa:

>>> re.findall(r'".+?"', text)                                                  
['"Francisco"', '"Ciência Programada"']

Agora temos o que queríamos. Mas ainda não é uma boa forma de obter os valores. Observe o que ocorreria se fossem passados campos vazios:

>>> text = 'nome="" site=""'                                                    

>>>re.findall(r'".+?"', text)                                                  
['"" site="']

Retorna errado pois o + exige no mínimo uma ocorrência. Na realidade, queremos 0 ou mais ocorrências, o que podemos resolver utilizando *:

>>> re.findall(r'".*?"', text)                                                  
['""', '""']

Entendendo o Match Object

Em diversos exemplos vimos que o resultado é um Match object. Vamos entender um pouco melhor esse objeto. Há quatro métodos importantes pro nível desse artigo:

>>> m = re.match(r'\d+', '12345')

>>> type(m)
re.Match

>>> m.group()  # retorna a string em que foi feito match
'12345'

>>> m.start()  # posição inicial do match
0

>>> m.end()    # posição final do match
5

>>> m.span()   # tupla com a posição inicial e final do match
(0, 5)

O método group será importante para a próxima seção.

Grupos de captura

Por vezes a string analisada possui diversos campos dos quais se deseja extrair dados. Podemos então definir grupos de captura. Os grupos são demarcados por parênteses e podemos repetir o conteúdo de um grupo com um qualificador de repetição, como *, +, ?, ou {m,n}. Por exemplo, (ab)* irá corresponder a zero ou mais repetições de ab.

Considere a tag de HTML a seguir da qual queremos extrair o nome (input) e os valores de type, id e name. Podemos criar um padrão, variável pattern no código, seguindo o formato da string. Em cada grupo queremos pegar uma ou mais ocorrências de caracteres e de forma preguiçosa, para não ocorrer os problemas vistos na seção de controle de repetições:

>>> html = '<input type="text" id="id_cpf" name="cpf">'

>>> pattern = r'<(.+?) type="(.+?)" id="(.+?)" name="(.+?)"'
# qualquer caractere antes de um espaço para o nome da tag, idem entre aspas para os demais grupos

>>> m = re.match(pattern, html)  # armazenando resultado do match em variável

>>> m
<re.Match object; span=(0, 41), match='<input type="text" id="id_cpf" name="cpf"'>

A variável m armazena o resultado, sendo um Match object. Por padrão, apresenta o match completo realizado, mas podemos explorar mais detalhes do objeto. O método groups apresenta todos grupos extraídos e o group permite retornar grupos específicos:

>>> m.groups()  # grupos de captura
('input', 'text', 'id_cpf', 'cpf')

>>> m.group(0)  # match inteiro
'<input type="text" id="id_cpf" name="cpf"'

>>> m.group(1)  # primeiro grupo
'input'

>>> m.group(2, 1, 3)  # grupos específicos
('text', 'input', 'id_cpf')

Suponha agora que pode ocorrer mudança na ordem dos atributos da tag HTML:

>>> html1 = '<input type="text" id="id_cpf" name="cpf">'

>>> html2 = '<input id="id_cpf" name="cpf" type="text">'

Claramente nosso padrão anterior não irá funcionar, pois dependia fortemente da ordem dos atributos. Já vimos o metacaractere | que indica alternativa, algo que será útil aqui pois queremos pegar cada atributo independente da ordem.

Outro conceito que utilizaremos é o de grupo de não captura, representado por (?:...), substituindo as reticências por uma expressão regular. Para entender, considere os exemplos a seguir:

>>> m = re.match('name="(.+?)"', 'name="Francisco"')                         

>>> m.groups()
('Francisco',)

>>> m = re.match('name="(?:.+?)"', 'name="Francisco"')

>>> m.groups()
()

No primeiro caso, utilizamos o padrão no grupo para obter o valor “Francisco” e no segundo excluímos esse grupo.

Podemos utilizar esse padrão alternadamente para obter cada grupo da tag HTML independente da ordem:

>>> pattern = r'<(.+?) (?:(?:type="(.+?)"|id="(.+?)"|name="(.+?)") ?)*'
# o grupo de fora indica a presença ou não de espaço

>>> m = re.match(pattern, html1)

>>> m
<re.Match object; span=(0, 41), match='<input type="text" id="id_cpf" name="cpf"'>

>>> m.groups()
('input', 'text', 'id_cpf', 'cpf')

>>> m = re.match(pattern, html2)

>>> m
<re.Match object; span=(0, 41), match='<input id="id_cpf" name="cpf" type="text"'>

>>> m.groups()
('input', 'text', 'id_cpf', 'cpf')

Grupos nomeados

Por fim, quando há vários grupos, é útil nomeá-los. O Python possui uma forma específica para indicar nome de cada grupo: ?P<nome>.

>>> pattern = r'<(?P<tag>.+?) (?:(?:type="(?P<type>.+?)"|id="(?P<id>.+?)"|name="(?P<name>.+?)") ?)*'

>>> m = re.match(pattern, html1)

>>> m.groups()
('input', 'text', 'id_cpf', 'cpf')

Quando se usa grupos nomeados, há um método muito útil, o groupdict, que retorna o nome do grupo e o valor na forma de um dicionário:

>>> m.groupdict() 
{'tag': 'input', 'type': 'text', 'id': 'id_cpf', 'name': 'cpf'}

>>> m = re.match(pattern, html2)

>>> m.groups()
('input', 'text', 'id_cpf', 'cpf')

>>> m.groupdict()
{'tag': 'input', 'type': 'text', 'id': 'id_cpf', 'name': 'cpf'}

Conclusão e um observação IMPORTANTE

A observação importante é: não é porque algo é possível de resolver com expressões regulares que deve ser resolvido com expressões regulares.

Especificamente em Python, manipulações de string envolvendo substituições costumam ser mais efetivas com métodos de strings como o replace. A própria documentação deixa isso claro.

Da mesma forma, por mais que tenha usado como exemplo o caso da tag HTML, há diversas discussões online sobre formas mais eficientes de extrair informações de HTML e porque regex não é, geralmente, a melhor opção. Veja aqui, aqui e aqui. Prepare-se para discussões acaloradas. E exercite o bom senso para saber quando é aceitável o uso e quando uma outra ferramenta deve ser utilizada.

Expressões regulares são úteis, mas podem ser muito difíceis de ler e debugar. Procure deixá-las o menor e mais específicas possível. É um assunto muito mais extenso que o apresentado aqui, tentei colocar o que considero um bom início e, obviamente, aquilo que está dentro do meu conhecimento. Certamente é uma ferramenta de conhecimento muito valioso.

Aproveite e leia outros artigos do site sobre Python.

Até a próxima!

Compartilhe:

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Rolar para cima