• Revista PROGRAMAR: Já está disponível a edição #53 da revista programar. Faz já o download aqui!

rolando2424

[Python] Tutorial de regex by PyTeam

12 mensagens neste tópico

Senhoras e senhores, aqui está o tão aguardado...

GUIA DO ROLANDO2424 PARA REGEX (Ex... ex... x...) <--- Fadeout

em Python

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á ;)) 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) (<---- Inserir piada sobre Viagra neste espaço ---->)

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

Vamos começar pelo início. O que é de facto uma regex.

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.

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 :D).

É 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.

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!

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 (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*

>>> 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 . é 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 { }.

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.

----- 8< ------ 8< ---- Cortar por aqui ---- 8< ------ 8< ----

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-me 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" 

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 | 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 "\".

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.

Esses atalhos são:

\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 [ \t\n\r\f\v] (whitespace, tabs, newlines, return carriages, etc.))

\S (Procurar por qualquer carácter que não seja um espaço em branco, ou seja, [^ \t\n\r\f\v])

\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.

            ____   ___ _____  _    _     _     

_ __ ___  |  _ \ / _ \_   _|/ \  | |   | |   

| '__/ _ \ | | | | | | || | / _ \ | |   | |   

| | |  __/_| |_| | |_| || |/ ___ \| |___| |___

|_|  \___(_)____/ \___/ |_/_/   \_\_____|_____|

                                               

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()
('sadfasdf\nsadfasdf\nsadfasdf\ndafg\n          adfg\n        ',)

E POR FIM...

        __     _______ ____  ____   ___  ____  _____

_ __ __\ \   / / ____|  _ \| __ ) / _ \/ ___|| ____|

| '__/ _ \ \ / /|  _| | |_) |  _ \| | | \___ \|  _| 

| | |  __/\ V / | |___|  _ <| |_) | |_| |___) | |___

|_|  \___(_)_/  |_____|_| \_\____/ \___/|____/|_____|

                                                     

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 

(	# 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()
(' ',)

E acho que é tudo.

Estava aqui a pensar numa piadinha para acabar o guia, mas não me estou a lembrar de nada.

No entanto, a oferta inicial de me arranjarem o email de uma moça que goste de regex continua em pé (ok, MUITA má escolha de palavras :P).


Tutorial na wiki em http://wiki.portugal-a-programar.org/python:regex :)

1

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Bah, o djthyrax podia ter movido com as mensagens todas para me massajar o ego ;)

Por acaso não falei de TUDO, mas sim do principal :P (ainda estava a pensar em falar em mais umas coisinhas).

Note to self: fazer todos os tutoriais em 3 horas :D

EDIT: Corrigi um erro que tinha numa parte do code (erro de syntax highlight do GeSHi).

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Como já tinha dito:

Epa isto está tão mas tão fantástico que nem tenho palavras para descrever. :ipool: <- parece que já tenhos os mesmos pensamentos que o triton :P

rolando, está mesmo espetacular ;)

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Alterei mais uma coisinha na explicação do ".". (Dizer que é o mesmo que [^\n] ;))

EDIT: Bem me parecia que me tinha esquecido de alguma coisa... Esqueci-me de colocar esta imagem no guia.

1316602552.png

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Podias ter aproveitado o tutorial que já está feito, também em nome da PyTeam, desde Novembro, para o melhorares/corrigires. Escusavas de ter tido a maior parte do trabalho ;)

Bom trabalho ainda assim, está mais limpo que o meu :P

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Podias ter aproveitado o tutorial que já está feito, também em nome da PyTeam, desde Novembro, para o melhorares/corrigires. Escusavas de ter tido a maior parte do trabalho ;)

Bom trabalho ainda assim, está mais limpo que o meu :P

Ops, não sabia que já havia um tutorial da pyteam de regex. Eu é que já tinha prometido ao djthyrax e ao tharis que fazia um quando acabasse os exames (eles chatearam-me a cabeça à uns meses).

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Muito bom :D

Só deixo uma pequena sugestão.. Exemplos ligeiramente mais complexos no fim, isto é, como aplicar em diversas situações, por exemplo:

(?=.{12,})(?=.*[a-z])(?=.*[A-Z])(?=.*[_])(?=.*[0-9])

Isto poderia ser usado para a verificação simples de uma password.. devendo ela ter pelo menos 12 caractéres, pelo menos 1 lowercase, 1 uppercase, 1 underscore e 1 numero..

Exemplos como este explicados devidamente enriqueciam o tutorial IMO ^^

Porque regex sem se perceber a lógica não dá muito certo :P

De resto, muito bom :P

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Crie uma conta ou ligue-se para comentar

Só membros podem comentar

Criar nova conta

Registe para ter uma conta na nossa comunidade. É fácil!


Registar nova conta

Entra

Já tem conta? Inicie sessão aqui.


Entrar Agora