Expressões Regulares
Expressões regulares são um mecanismo muito poderoso para manipulação de strings, também conhecido como regex ou regexp, é um campo de estudo muito abrangente na ciência da computação teórica e linguagens formais.
Pode-se dizer que é uma sequência de caracteres que define um padrão de busca, normalmente este padrão é usado por algoritmos de busca de strings, onde é feito uma busca por determinado padrão, seja para encontrá-lo ou até mesmo encontrá-lo e alterá-lo, também é muito usado para validação de input de dados.
O conceito surgiu na década de 1950, quando o matemático americano Stephen Cole Kleene formalizou a descrição de uma linguagem regular. O conceito entrou em uso comum com utilitários de processamento de texto do Unix. Diferentes sintaxes para escrever expressões regulares existem desde a década de 1980, sendo uma o padrão POSIX e outra, amplamente utilizada, a sintaxe Perl.
Expressões regulares são usadas em search engines, search and replace dialogs de processadores de texto e editores de texto, em utilitários de processamento de texto como sed e AWK e em análise lexical. Muitas linguagens de programação fornecem recursos de regex integrados ou por meio de bibliotecas, como é o caso da linguagem Python que nos oferece a biblioteca re.
Introdução
Uma expressão regular, geralmente chamada de padrão, especifica um conjunto de strings necessárias para um propósito específico. Uma maneira simples de especificar um conjunto finito de strings é listar seus elementos ou membros. No entanto, muitas vezes existem maneiras mais concisas, por exemplo, o conjunto contendo as três cadeias de caracteres "Handel", "Händel" e "Haendel" pode ser especificado pelo padrão H(ä|ae?)Ndel
, dizemos que esse padrão corresponde a cada uma das três strings. Na maioria dos formalismos, se existe pelo menos uma expressão regular que corresponde a um determinado conjunto, então existe um número infinito de outras expressões regulares que também correspondem a ela - a especificação não é única.
Âncoras: ^
e $
Exemplo | Descrição | URL |
---|---|---|
^Py | Encontra qualquer string que comece com Py | https://regex101.com/r/cO8lqs/4303 |
on$ | Encontra uma string que termina com on | https://regex101.com/r/cO8lqs/4304 |
^Python$ | Encontra a string Python | https://regex101.com/r/cO8lqs/4305 |
Python | Encontra qualquer string que tenha Python em si | https://regex101.com/r/cO8lqs/23892 |
Quantificadores: *
, +
, ?
e {}
Exemplo | Descrição |
---|---|
abc* | Encontra uma string que tenha ab seguido por zero ou mais c |
abc+ | Encontra uma string que tenha ab seguido por um ou mais c |
abc? | Encontra uma string que tenha ab seguido por zero, ou um c |
abc{2} | Encontra uma string que tenha ab seguido por dois c |
abc{2,} | Encontra uma string que tenha ab seguido por dois ou mais c |
abc{2,5} | Encontra uma string que tenha ab seguido por 2 até no máximo 5 c |
a(bc)* | Encontra uma string que tenha a seguido por zero ou mais cópias da sequência bc |
a(bc){2,5} | Encontra uma string que tenha a seguido por 2 até no máximo 5 cópias da sequência bc |
Operador OU: |
e []
Exemplo | Descrição |
---|---|
a(b|c) | Encontra uma string que tenha a seguido por b ou c |
a[bc] | Igual o anterior, ambas as notações podem ser usadas e produzem o mesmo resultado |
Classes de caracteres: \d
, \w
, \s
e .
Exemplo | Descrição |
---|---|
\d | Encontra um único caracter que seja um dígito |
\w | Encontra um caracter de palavra (caracter alfanumérico e underline) |
\s | Encontra caracteres de espaço em branco (incluindo tabs e quebra de linha) |
. | Encontra qualquer caracter |
Negação de classe de caracteres: \D
, \W
, \S
Exemplo | Descrição |
---|---|
\D | Faz a operação inversa de \d e traz todos os não-dígitos |
\W | Faz a operação inversa de \w e traz todas as não-palavras |
\S | Faz a operação inversa de \s e traz todos os não-espaços |
Outro ponto importante, é caso queiramos dar escape, por exemplo validar um .
, para isso usamos o \.
O mesmo vale caso seja necessário validarmos $
, utilizamos $
Experimente mais opções de expressões em Regex101
Expressões Regulares em Python
Python já possui incluído o módulo re que utilizaremos para nossas expressões regulares, este módulo fornece operações de correspondência de expressões regulares semelhantes às encontradas em Perl.
Para usá-lo precisamos importá-lo, sem a necessidade de instalação, vamos também inspecionar o módulo para termos uma visão do que ele nos disponibiliza em termos de atributos e métodos.
import reprint(dir(re))# ['A', 'ASCII', 'DEBUG', 'DOTALL', 'I', 'IGNORECASE', 'L', 'LOCALE', 'M', 'MULTILINE', 'S', 'Scanner', 'T', 'TEMPLATE', 'U', 'UNICODE', 'VERBOSE', 'X', '_MAXCACHE', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '__version__', '_alphanum_bytes', '_alphanum_str', '_cache', '_cache_repl', '_compile', '_compile_repl', '_expand', '_locale', '_pattern_type', '_pickle', '_subx', 'compile', 'copyreg', 'error', 'escape', 'findall', 'finditer', 'fullmatch', 'match', 'purge', 'search', 'split', 'sre_compile', 'sre_parse', 'sub', 'subn', 'sys', 'template']
Veja que temos bastantes opções para trabalharmos, inicialmente vamos ver os métodos mais relevantes através de diversos exemplos, mas primeiramente é importante entendermos o conceito de raw string, pois são elas que vamos utilizar em nossas expressões regulares.
Uma raw string do Python é uma string normal, prefixada com r ou R. Isso trata caracteres como barra invertida (\
) como um caractere literal. Isso também significa que esse caractere não será tratado como um caractere de escape, por exemplo:
print('Hello\nWorld')# Hello# World
Veja que o output é tratado como esperado, o escape caracter \n
é interpretado como uma quebra de linha, já no caso de uma raw string, ele será tratado como um caractere literal:
print(r'Hello\nWorld') # Hello\nWorld
Agora que sabemos como as raw strings funcionam, vamos ver os métodos de expressões regulares.
findall()
O método findall() retorna todas as porções correspondidas como uma lista de strings. Ele conta com os seguintes argumentos:
- padrão
- string
- flags
O primeiro argumento é o padrão que desejamos testar e extrair em relação à string de input, que é o segundo argumento. O último argumento flags é opcional. Vamos então aos exemplos para uma melhor compreensão:
resultados = re.findall(r'ab*c', 'abc ac adc abbbc abbb')print(resultados) # ['abc', 'ac', 'abbbc']
No exemplo acima usamos o quantificador *
, que indica 0 ou mais b's.
resultados = re.findall(r'ab+c', 'abc ac adc abbbc abbb')print(resultados) # ['abc', 'abbbc']
No exemplo acima usamos o quantificador +
, que indica 1 ou mais b's.
Vejamos como podemos definir uma expressão regular para encontrar caracteres de palavras:
import rex = "Expressões regulares são legais"r = re.findall(r"\w+", x)print(r) # ['Expressões', 'regulares', 'são', 'legais']
Como podemos ver, todos os caracteres da sentença foram correspondidos e recebemos uma lista deles.
split()
O método split() divida a string pelas ocorrências do padrão.
import ree = "Eu sou uma expressao com diversas palavras"r = re.split(r'\s', e)print(r) # ['Eu', 'sou', 'uma', 'expressao', 'com', 'diversas', 'palavras']
Como podemos ver, a função split() separou a expressão em uma lista de palavras, usando o separador \s
que representa o espaço em branco.
Vamos agora usar um dígito como separador:
string = 'um:1 dois:2 quinze:15 trinta:30'padrao = r'\d+'resultado = re.split(padrao, string)print(resultado) # ['um:', ' dois:', ' quinze:', ' trinta:', '']
search()
Normalmente, usamos o operador in para testar se uma string faz parte de outra string ou não. Para expressões regulares, usamos a função search() cuja lista de argumentos é mostrada abaixo:
- padrão
- string
- flags
O primeiro argumento é o padrão de expressão regular que você deseja testar contra a string de input, que é o segundo argumento, flags é opcional, ajuda a alterar o comportamento padrão dos padrões de expressões regulares.
Como uma boa prática, é interessante usar raw strings para construir o padrão de expressão regular. Vejamos então alguns exemplos:
import resentenca = "esta é uma string"print('esta' in sentenca) # Trueprint('este' in sentenca) # Falseprint(bool(re.search(r'esta',sentenca))) # Trueprint(bool(re.search(r'este',sentenca))) # Falseprint(type(re.search(r'esta',sentenca))) # <class 're.Match'>print(type(re.search(r'este',sentenca))) # <class 'NoneType'>
O valor de retorno da função search() é um objeto re.Match
quando há uma correspondência, caso contrário o resultado é NoneType
.
Vejamos agora um exemplo usando o argumento flags:
print(bool(re.search(r'Esta',sentenca))) # Falseprint(bool(re.search(r'Esta',sentenca, flags=re.I))) # True
A flag re.I
significa IGNORECASE é uma flag para permitir correspondência sem distinção entre maiúsculas e minúsculas.
Vejamos agora um exemplo com expressões geradoras:
palavras = ['cachorro','macarrão','macaco'][p for p in palavras if re.search(r'rr',p)] # ['cachorro', 'macarrão']all(re.search(r'ca',p) for p in palavras) # Trueany(re.search(r'xa',p) for p in palavras) # False
Vejamos agora como podemos utilizar um quantificador em nossa padrão:
import repadrao = "a*b"r = re.search(padrao, "aaaaaaabcedefg")print(r) # <_sre.SRE_Match object; span=(0, 8), match='aaaaaaab'>print(r.group()) # aaaaaaab
Como podemos ver, primeiro nos foi retornado um objeto match, posteriormente acessamos a string encontrada através do método group() que nos traz o resultado, também podemos aplicar métodos como start(), end() e span().
sub()
Para busca e substituição normais, podemos usar o método str.replace(). Para expressões regulares, usamos a função sub(), cuja lista de argumentos é mostrada abaixo:
- padrão
- substituição
- string
- count
- flags
O primeiro argumento é o padrão de expressão regular a ser correspondido em relação à string de input, que é o terceiro argumento. O segundo argumento específica a string que irá substituir as porções correspondidas pelo padrão da expressão regular. Os últimos dois argumentos são opcionais. Para entendermos melhor este conceito, vamos ao exemplo:
frase = 'eu gosto de pizza'print(re.sub(r'z','Z',frase)) # eu gosto de piZZaprint(re.sub(r'z','Z',frase,count=1)) # eu gosto de piZza
Observe que o argumento count limita o número de substituições.
Compilando Expressões Regulares
Expressões Regulares podem ser compiladas usando a função compile(), que nos retorna um objeto re.Pattern
.
As funções do módulo re de nível superior estão todas disponíveis como métodos para tais objetos. Compilando uma expressão regular é útil se ela tiver que ser usada em vários lugares ou chamada em vários vezes dentro de um loop (benefício de velocidade).
Vamos começar compilando uma expressão regular:
animal = re.compile(r'gato')print(type(animal)) # <class 're.Pattern'>
Usaremos agora o o método search() no padrão definido por nós para buscar pelo padrão em uma determinada string:
bool(animal.search('o gato é bonito')) # Truebool(animal.search('o rato é roedor')) # False
Também podemos usar o método sub neste padrão, de forma a executar substituções:
animal.sub('rato','o gato fugiu do cachorro')# 'o rato fugiu do cachorro'
match()
O método match() funciona da seguinte forma: Se zero ou mais caracteres no início da string corresponderem a expressão regular informada, retorna um objeto de re.Match
correspondente. Retorna None
se a string não corresponder ao padrão.
Neste exemplo vamos usar um padrão simples para darmos match em um email:
import repadrao = r"\w+@(\w+\.)+(com|org|net)"r = re.match(padrao,"teste@teste.org")print(r) # <re.Match object; span=(0, 15), match='teste@teste.org'>print(r.group()) # teste@teste.org
Vimos que nesse caso, foi possível dar match no e-mail teste@teste.org
, nos mostrando assim a relevância e importância das expressões regulares dentro da computação, um assunto relevante e de compreensão necessária, pois está presente em praticamente todas as linguagens de programação e sua aplicabilidade é muito vasta.