Ferramentas de Utilizador

Ferramentas de Site


dev_geral:python:regex

Expressões Regulares em Python

Comentário do autor

Antes de começar a chatear a cabeça às pessoas, é melhor eu dizer umas coisinhas sobre Regex (também conhecidas por Regular Expressions).

  • Regexs não é complicado de escrever, pode ser complicado é de ler as regexs feitas pelos outros (existe uma maneira de atenuar/evitar essa situação que eu vou explicar mais para a frente, mas se são impacientes, procurem por re.VERBOSE aqui no guia, que não falham).
  • Regexs não é a cura para todos os males. O que quero dizer é que nem sempre regexs são a melhor maneira de atacar um problema. Existe um ditado (o livro onde isso está escrito está na casa-de-banho e não me apetece ir lá :-P) que é algo assim "When confronted with a problem, people often think 'I know, I'll use Regular Expressions'. Now they have two problems."
  • Da mesma forma, usar regex não vos torna l33t, nem faz com que as raparigas se apaixonem por vocês (no entanto, se arranjarem alguma dessas, dêem-me o email dela :-P)

Bem, mas vamos começar com o que interessa.

O que são Expressões Regulares

De forma muito simples, uma regex é uma string especial que permite fazer comparações com outra string, utilizando uma espécie de linguagem de programação própria.

Expressões Regulares em Python

Vou já mostrar um exemplo básico, mas primeiro temos que saber que módulo importar.

>>> import re

E está feito.

O passo seguinte é escrever a string especial, utilizando o re.compile(). Depois é só fazer a comparação com outra string para ver o que acontece, usando neste caso o re.search().

>>> pattern = re.compile("lol")
>>> string_a_comparar = "dsgsdfgloldfgdfsg"
>>> re.search(pattern, string_a_comparar)
<_sre.SRE_Match object at 0x854f838>

Reparem que a string "lol" e incluída na string "string_a_comparar". O método re.search, recebe a string especial (neste caso, a variável pattern) e a string a comparar (adivinhem qual é :-D).

Se o método re.search encontrar a pattern na string a comparar, faz return de um match object que tem alguns métodos que vou explicar mais à frente (mas só vou explicar alguns :-P).

É claro que isto é tudo muito bonito e tal mas não seria mais simples fazer:

>>> "lol" in string_a_comparar
True

Por acaso era mais simples E mais eficiente. Este é um daqueles casos em que não é necessário usar regex.

Caracteres especiais

Como regex é uma espécie de linguagem própria, existem caracteres que têm algum significado especial quando usados na pattern.

Eles são:

( )
[ ]
^ $
.
* + ? { }
| 

Então aqui vai uma explicação destes caracteres todos.

Tudo o que escreverem dentro dos ( ) é guardado quando fizerem re.search() numa variável para poderem aceder mais tarde usando re.search().groups().

Por exemplo:

>>> pattern = re.compile("(lol)")
>>> re.search(pattern, string_a_comparar).groups()
('lol',)

Isto pode parecer estúpido inicialmente, pois se estamos a verificar que o "lol" existe na string a comparar, é claro que ele vai lá estar no groups(). Mas isto vai servir para muita coisa, já daqui a um bocado.

NEXT!

Utilizar [ ] para designar alternativas

Os [ ] servem para indicar alternativas. Isto é melhor mostrar um exemplo do que explicar. Por exemplo, [abcdef] vai procurar por a OU b OU c OU d OU e OU f. Por acaso nem é necessário escrever [abcdef], podendo-se usar simplesmente [a-f] (os caracteres de "a" a "f").

EXEMPLO TIME!

>>> pattern = re.compile("([abcdef])")
>>> re.search(pattern, string_a_comparar).groups()
('d',)

OPS! O re.search apenas fez return do primeiro "d" (que é a primeira letra da string_a_comparar que existe na regex "[abcdef]")

Então o que fazer? Bem, há duas coisas a fazer. Primeiro, pode-se modificar a regex para incorporar repetição (que vou explicar mais à frente), ou pode-se usar outro método para além do re.search. Por agora vamos utilizar outro método, o re.findall() (só quero explicar como fazer repetições mais para a frente :D).

*Mete os tambores a soar* ABRAM ALAS PARA O re.findall() *Dá duas chapadas no tipo do tambor*

Utilizar re.findall() e os caracteres especiais ^ e $

>>> pattern = re.compile("[abcdef]")
>>> re.findall(pattern, string_a_comparar)
['d', 'd', 'f', 'd', 'f', 'd', 'f']
 
>>> pattern = re.compile("[a-f]")
>>> re.findall(pattern, string_a_comparar)
['d', 'd', 'f', 'd', 'f', 'd', 'f']

Podem ver também que [abcdef] é o mesmo que [a-f].

No entanto, existe também outro carácter que podem usar dentro dos [ ] que também tem um significado especial, o ^ no início do [ ]. O ^ faz com que a regex procure aquilo que não esteja dentro dos [ ]. Por exemplo, [^012345] ( ou então [^0-5]) só ia procurar por 6, 7, 8 e 9 e os restantes caracteres ("a", "b", "c", ".", etc.).

>>> pattern = re.compile("[^0-5]")
>>> re.findall(pattern, "129.86sfg79sd79827")
['9', '.', '8', '6', 's', 'f', 'g', '7', '9', 's', 'd', '7', '9', '8', '7']

No entanto o ^ também tem um significado especial se tiver fora dos [ ] que vou dizer de seguida.

Os caracteres ^ e $ servem para indicar o início e fim de uma respectivamente.

Por exemplo:

>>> pattern = re.compile("^lol") # ^ corresponde ao inicio da string
>>> re.search(pattern, "lolasdfasdf")
<_sre.SRE_Match object at 0x860ad78>
 
>>> re.search(pattern, "1234lolasdfasdf") # Isto nao vai funcionar porque o "lol" nao se encontra no inicio da string
 
>>> pattern = re.compile("lol$") # $ corresponde ao fim da string
>>> re.search(pattern, "asdfasdflol")
<_sre.SRE_Match object at 0x856b838>
 
>>> re.search(pattern, "asdfasdlolf") # Isto nao vai dar funcionar porque o "lol" nao se encontra no fim da string

O carácter especial . (ponto)

O . é o maior do bairro. Basicamente ele apanha todos os caracteres, excepto o newline (n), e existe a possibilidade de fazer com que ele apanhe mesmo os n (vou explicar isso mais à frente, quando falar dos modos do re.compile(), mas não aguentam esperar, procurarem mais abaixo por re.DOTALL. Acreditem em mim que não falham )

Um . é o mesmo que ter [a-zA-Z0-9 ] (o espaço no final é propositado, serve para fazer o regex apanhar também os espaços em branco) mais uns caracteres especiais para coisas como tabs, etc., ou melhor ainda [^n].

>>> pattern = re.compile(".")
>>> re.findall(pattern, "sl234 kgj")
['s', 'l', '2', '3', '4', ' ', 'k', 'g', 'j']

Agora vem os caracteres para as repetições, *, +, ? mais os { }.

Como procurar por caracteres repetidos

Vamos começar pelo "*". Este símbolo significa "procurar por 0 ou mais vezes".

Por exemplo:

>>> pattern = re.compile("lo*l")
>>> re.search(pattern, "ll")
<_sre.SRE_Match object at 0x860a988>
 
>>> re.search(pattern, "lol")
<_sre.SRE_Match object at 0x860a368>
 
>>> re.search(pattern, "loooooooooooooooool")
<_sre.SRE_Match object at 0x860a988>

O "+" é parecido com o "*", excepto que significa "procurar por 1 ou mais vezes".

EXEMPLO-emplo-plo-o-o-o-o ←– mais fadeout

>>> pattern = re.compile("lo+l")
>>> re.search(pattern, "ll") # Nao da nada, porque o "o" tem que existir pelo menos uma vez
 
>>> re.search(pattern, "lol")
<_sre.SRE_Match object at 0x860af00>
 
>>> re.search(pattern, "loooooooooooooooool")
<_sre.SRE_Match object at 0x860ad40>

O "?" é também parecido com o "*" e o "+", excepto que significa "procurar por 0 ou 1 vezes".

Adivinhem o que vem aí (dica, começa por E e acaba em xemplo).

>>> pattern = re.compile("lo?l")
>>> re.search(pattern, "ll")
<_sre.SRE_Match object at 0x860a9c0>
 
>>> re.search(pattern, "lol")
<_sre.SRE_Match object at 0x860acd0>
 
>>> re.search(pattern, "lool") # Nao resulta em nada porque o "o" existe mais do que uma vez

Antes de passar aos { } quero falar de uma coisa muito importante.

A SÉRIO! É IMPORTANTE! (estejam atento pelo menos a esta parte).

Aliás, isto é tão importante que o melhor que têm a fazer é imprimir esta página e recortar esta parte e colar à beira do monitor, do espelho da casa-de-banho, debaixo da almofada e no frigorífico (podem arranjar ímanes num disco rígido velho se for preciso).

Até faço o picotado para vos ser mais fácil recortar.

- - - - - - - - - - - - - - - Cortar por aqui - - - - - - - - - - - - - -

Imaginemos que queremos recolher o conteúdo das tags de uma página de HTML (vamos simular uma página de HTML com a string "<p> <img href='lol' /> df </p>").

A primeira ideia provavelmente seria usar uma regex do género "<(.*)>", ou seja, procurar por todos os caracteres que apareçam 0 ou mais vezes (é para isso que o ".*" serve), guardar os valores para usar mais tarde ( é para isso que os () servem), e que estejam dentro de < > (estes caracteres não têm nenhum significado especial para a regex), e provavelmente usar o re.findall(), senão só ia reagir ao primeiro <p>.

Muito bem, vamos testar esta teoria.

>>> pattern = re.compile("<(.*)>")
>>> re.findall(pattern, "<p> <img href='lol' /> df </p>")
["p> <img href='lol' /> df </p"]

Ok… Não era bem esta a ideia que queríamos/queria (se não percebam o que tinha dito antes, a ideia seria obter algo do género ["p", "img href='lol' /", "/p"]).

Vamos então lá ver o que aconteceu.

O problema é que o "*" e o "+" fazem aquilo que se chama "greddy match". Isso significa que o regex vai tentar apanhar o máximo da string possível, ou neste caso, do primeiro "<" ao último ">" existente na string (que se encontra em no "</p>"). Como a inveja é um pecado mortal (pelo menos na religião cristã), e não queremos que nenhuma regex vá para o /dev/null, temos que usar o que se chama de "non-greddy match".

E como é que se faz isso? Simples, adiciona-se um ? depois do ".*". Ou seja:

>>> pattern = re.compile("<(.*?)>") # I'M ROBIN HOOD! (not greedy)
>>> re.findall(pattern, "<p> <img href='lol' /> df </p>")
['p', "img href='lol' /", '/p']

E cá está o que queríamos/queria.

Agora porque razão é que disse para prestarem muita atenção a isto? Porque o "?" pode também significar, tal como já tinha dito, "procurar o carácter 0 ou 1 vezes", e também porque é muito importante saber o que é uma "greddy match" e uma "non-greddy match" (nem vos passa as dores de cabeça que passei antes de descobrir isto :P).

Ou seja, se o "?" estiver depois de um "*" ou um "+", significa "non-greddy match", senão significa "procurar o carácter 0 ou 1 vezes".

—– 8< —— 8< —- Cortar por aqui —- 8< —— 8< —-

Continuando com a lição para os { }, que não me esqueci deles :P.

Os { } levam dois valores lá dentro, por exemplo {2,5}, (não pode ser {2, 5}, tem que ser tudo junto) que significa "apanhar o carácter se ele estiver repetido 2, 3, 4 ou 5 vezes".

Vamos lá ver:

>>> pattern = re.compile("lo{2,5}l")
 
>>> re.search(pattern, "lol") # Nao aceita, pois so tem um "o"
 
>>> re.search(pattern, "lool") # Aceita, pois tem dois "o"
<_sre.SRE_Match object at 0x860afa8>
 
>>> re.search(pattern, "loool") # Aceita, pois tem três "o"
<_sre.SRE_Match object at 0x8614058>
 
>>> re.search(pattern, "looool") # Aceita, pois tem quatro "o"
<_sre.SRE_Match object at 0x8614640>
 
>>> re.search(pattern, "loooool") # Aceita, pois tem cinco "o"
<_sre.SRE_Match object at 0x8614800>
>>> re.search(pattern, "looooool") # Nao aceita, pois tem mais seis "o", que e mais de cinco "o" :P

Se não escrevem um valor, o python usa o valor mais lógico para preencher essa falha. Por exemplo, {,5} transforma-se em {0,5} e {5,} transforma-se em {5,+infinito} (ok, nada na informática é infinito em termos de armazenamento, mas é utilizado o maior valor possível (alguns biliões), que, acho eu, corresponde ao valor máximo do int em C, mas se arranjarem uma string com biliões de caracteres então o vosso problema não está na regex :P).

Como eu sei que são todos uns tipos/as muito inteligentes, estou certo que já reparam que "*" é o mesmo que {0,} ou {,} , "+" é o mesmo que {1,} e "?" (inserir aviso sobre evitar a confusão com o símbolo de "non-greddy match" aqui) é o mesmo que {0,1}.

Porém é melhor usarem os símbolos "*", "+", "?", para facilitar a leitura das regex (acreditem que ajuda a ler E ainda poupam as teclas do teclado).

Vamos agora falar do |.

O carácter especial |

O | apenas significa OU. Por exemplo, usar "lol|lulz" significa procurar por "lol" ou "lulz".

>>> pattern = re.compile("lol|lulz")
 
>>> re.search(pattern, "lol") # Activa porque esta no conjunto ("lol", "lulz")
<_sre.SRE_Match object at 0x861f790>
 
>>> re.search(pattern, "lulz") # Activa porque esta no conjunto ("lol", "lulz")
<_sre.SRE_Match object at 0x861fb10>
 
>>> re.search(pattern, "SPARTAAAAAAAA") # Nao activa porque nao esta no conjunto ("lol", "lulz")

E por fim só falta falar de um carácter que é o "".

O carácter especial

Tudo o que ele faz é transformar um carácter especial num carácter normal. Por exemplo, se quiserem procurar por um "[" ou por um "]", não podem simplesmente colocar um "[" ou "]" na regex, por esses são dois caracteres que tem um significado muito específico na regex. Como tal têm que dizer ao módulo re para ignorar esses caracteres, ou colocar um antes deles. Ou seja, em vez de "[" "]" passamos a usar "[" e "]".

No entanto existe um problema com a forma com o Python trata o carácter . É que também ele significa para o Python ignorar o carácter seguinte. Teriam então que usar "[" ou "]", OU (a maneira mais indicada neste caso) escrever a string da regex no seguinte formato:

>>> pattern = re.compile(r"[ ]")
>>> re.search(pattern, "12[ ]21")
<_sre.SRE_Match object at 0x8614758>

Reparem no "r" que está no pattern. Para quem não sabe, colocar um "r" antes de uma string (qualquer que seja), indica ao Python para ignorar todos os caracteres considerados pelo Python (não pelo módulo re). O "r" vem de "raw". Por exemplo:

>>> print "nteste"
 
teste
>>> print r"nteste"
nteste

Por isso já sabem, se quiserem usar o numa regex, não se esquecem de transformar essa string numa string "raw". Senão podem acabar com algo assim: "", ou seja, dizer ao python para ignorar dois , que por sua vez diz ao módulo re para ignorar um :P

E são estes os caracteres especiais todos de uma regex em Python. (Hum… Acho que me alonguei um bocado, o Vim indica quase 313 linhas escritas… Bem lá tem que ser).

Teoricamente, podem escrever qualquer regex usando o que está escrito até agora, mas existem uns atalhos jeitosos para algumas das combinações mais usadas.

Atalhos para expressões comuns

Estes atalhos deveram de ser introduzidos na pattern para activar o seu efeito.

d (Procurar qualquer dígito, ou seja [0-9]) D (Procurar por qualquer não dígito, ou seja [^0-9])

s (Procurar por qualquer espaço em branco, ou seja [ tnrfv] (whitespace, tabs, newlines, return carriages, etc.)) S (Procurar por qualquer carácter que não seja um espaço em branco, ou seja, [^ tnrfv])

w (Procurar por qualquer carácter alfanumérico, ou seja [a-zA-Z0-9_]). W (Procurar por um carácter que não seja alfanumérico, ou seja, [^a-zA-Z0-9_]).

Por exemplo, podem usar [d|s] para procurar por números e espaços em branco.

Hum… Acho que só me falta falar numa coisa.

Lembram-se de eu ter falado sobre os modos do re.compile, à cerca de 300 linhas atrás? Pois bem, chegou a hora de falar neles. :P

Existem alguns modos, mas eu só vou falar de dois, o re.DOTALL e o re.VERBOSE.

re.DOTALL

Este modo apenas faz com o carácter especial "." apanhe também o "n" (normalmente pára quando encontra esse carácter). Isto é bom por exemplo se tivermos uma string com várias linhas.

>>> pattern = re.compile("<(.*)>", re.DOTALL)
>>> re.search(pattern, """<sadfasdf
sadfasdf
sadfasdf
dafg
          adfg
        >""").groups()
('sadfasdfnsadfasdfnsadfasdfndafgn          adfgn        ',)

E POR FIM…

re.VERBOSE

Este é o modo que mais contribui para se poder escrever regex que sejam legíveis. Basicamente, neste modo, o módulo re ignora todo o espaço em branco que não esteja dentro de [ ], e permite também escrever comentários dentro da regex.

Vamos lá ver:

>>> pattern = re.compile(""" # Nao se esquecam de usar 3 ' para indicar uma string que dura varias linhas
	# Ah, qualquer coisa escrita apos um # e considerado um comantario
	# Tal como no Python normal :P
 
(	# Comecar a gravar informacao
[a-c ]	# Procurar por a, b, c ou um espaco em branco
)	# Terminal gravacao
$ 	# Ainda se lembram o que isto significa? Serve para mostrar que queremos esta informacao no fim da string a procurar
""", re.VERBOSE)
 
>>> re.search(pattern, "lldldldldldla").groups()
('a',)
 
>>> re.search(pattern, "lldldldldldl ").groups()
(' ',)
dev_geral/python/regex.txt · Esta página foi modificada pela última vez em: 2018/05/14 21:37 (Edição externa)