usando expressões regulares com o Python


Usando expressões regulares com Python

Expressões regulares

Uma expressão regular (regular expression, ou mais simples e popular, regex) é uma sequência de caracteres que define um padrão para buscas em textos.
Os caracteres que compõem uma expressão regular podem ser caracteres literais (isto é, representam a sí mesmos numa busca em textos) ou meta-caracteres que agrupados significam uma representação específica de diversos caracteres textuais para uma busca em texto.
Por exemplo, isto é um regex: abc\d{3} O significado desta expressão descrito em palavras seria o seguinte:
letra a seguida de uma letra b seguida de uma letra c seguidos por 3 digitos consecutivos
Neste exemplo a, b e c são literais, representando a sí mesmos. Já o caracter \ é um metacaracter chamado escape e que significa
o caracter seguinte, d, deve ser lido como um metacaracter e não um literal
Desta forma o d passa a ser lido como outro metacaracter e não a letra d ela mesma. No caso os dois metacaracteres \d significam
um digito (qualquer um digito)
Finalmente a construção {3} seguindo o \d significa (a chave { é tambem um meta-caracter):
repita 3 vezes o metacaracter anterior
o qual, \d, como dissemos representa um digito.
Resumindo o exemplo, o regex abc\d{3} representa um padrão que casa (match) qualquer uma das 1000 sequências textuais tais como
abc123 abc987 abc000 e assim por diante...
mas que NÃO casa outras sequências que não seguem o padrão, tais como
abb123, ab456, abc67 e assim por diante

Expressões regulares são muito úteis para fazer buscas textuais. Quase todas linguagens de programação modernas tem algum tipo de suporte para utilizar regex sob contrôle do programa.
Por outro lado o uso de regex é um tópico difícil para quem não as utiliza frequentemente. A sintaxe dos regex é convoluta e efeitos colaterais indesejados na busca são frequentes e inesperados. Muita prática e boas referências são necessárias para bom uso desta ferramenta poderosa.
Dito tudo isto, nesta lição não vamos estudar regex, além de descrição sucinta de tópicos na medida do necessário. Isto é, assumimos alguma familiaridade sua com este assunto.
O assunto regex foi tópico de palestra e mini curso que demos no NECI no começo de 2016. A palestra em formato Powerpoint pode ser baixada aqui.
Junto com a palestra houve um mini curso baseado num software desenvolvido especialmente para o NECI. Este programa (versão Windows) mais arquivos acessórios também podem ser baixados aqui
Caso você não tenha nunca visto regex baixe estes itens agora e estude-os antes de continuar esta lição.

Meta caracteres

Para sua conveniência aqui vai a descrição de parte dos meta-caracteres usados em expressões regulares:
*     Casa zero, uma ou mais vezes o caracter precedente     Ah* casa "Ahhhhh" ou "A"
?     Casa zero ou uma vez o caracter precedente     Ah? casa "Al" ou "Ah"
+     Casa uma ou mais vezes o caracter precedente     Ah+ casa "Ah" ou "Ahhh" mas não "A"
\     Usado para escape de caracter especial     Feliz\? casa "Feliz?"
\d     Casa um caracter digito: [0-9]     A\d+ casa "A123" mas não "A"
\s     Casa um caracter whitespace: [ \t\r\n\f]     ban\s+ana casa "ban ana"
\S     Casa um caracter non-whitespace: [^ \t\r\n\f]     \S casa "a" em " abc "
\w     Casa um caracter alfanumerico: [A-Z_a-z]     \w\w casa "aB"
\W     Casa um caracter non-alphanumeric: [^A-Z_a-z]     \W\W\W casa "#$;" em "pontuação#$;"
\b     Ancora posição de fronteira de palavra     \b casa uma posição em vez de um caracter. A posição casada pode ser:

    Imediatamente antes do primeiro caracter de um string, se o primeiro caracter for um \w.
    Imediatamente após o último caracter de um string, se o último caracter for um \w.
    Entre dois caracters de um string, onde um caracter é um \w e o outro caracter é um \W

.     Caracter "wildcard" (coringa), casa qualquer caracter, menos o fim de linha \n     le.* casa "ler", "leem"
( )     Grupos e subexpressões     Veja exemplo para |
[ ]     casa um range (extensão) de caracteres     [cbf]ar casa "car", "bar", ou "far"
[0-9]+ casa qualquer inteiro positivo
[a-zA-Z] casa as letras a-z (maiúsculas e minúsculas)
[^0-9] casa qualquer caracter não- 0-9.
|     casa expressão ou grupo precedente ou seguinte     (segunda|terça)-feira casa "segunda-feira" ou "terça-feira"
{ }     casa um numero de ocorrências específico do precedente     [0-9]{3} casa "315" mas não "31"
[0-9]{2,4} casa "12", "123", and "1234" mas não "1"
[0-9]{2,} casa "1234567..."
    ^     Ancora início de um string. Dentro (no início) de range [] significa negação do que segue.     
[^0-9] casa qualquer caracter diferente de [0-9].
$     Ancora fim de um string.     im$ casa "fim" mas não "final"


Criar um regex consiste em descrever corretamente um padrão de texto desejado, usando caracteres literais e meta caracteres.
Por exemplo, o padrâo descrito assim:
Três digitos, seguidos de quatro letras minusculas
poderia ser codificado num regex deste modo:
**\d\d\d[a-z]{4}**

Regex e Python

Como de hábito usaremos um módulo conveniente para criar e usar regex em nosso programas em Python.
No caso este é o módulo re (regular expressions). A documentação oficial para este poderoso módulo esta aqui, e inclue uma boa introdução ao assunto.
Recomendo fortemente a leitura e recurso frequente a esta documentação. Dúvidas vão surgir e a documentação sempre ajuda. Em particular logo na primeira página eles descrevem todos os meta-caracteres e seu significado.
O módulo re oferece diversas funções e objetos associados que são úteis numa busca em texto. Destes veremos somente alguns que nos parecem mais úteis, mas outras podem ser vistas na documentação do módulo se desejado.
Vejamos algumas funções básicas.

Raw String Notation

No Python usamos a notação r'um string qualquer' (repare na letra r antes do string) para indicar que o string que segue não deve ser lido como se fora uma expressão regular. Isto permite, por exemplo, que possamos escrever caminhos de arquivos e urls sem ter que ficar digitando duas barras \\ cada vez que escrever uma barra \.
r'C:\Users\ps\Desktop\python'
em vez de
'C:\\Users\\ps\\Desktop\\python'

re.search( pattern, string)

A função re.search( padrao, string) percorre o string dado no argumento e procura nele o primeiro casamento com o regex padrao.
A função devolve um objeto da classe Match que nos permite obter o subtexto encontrado, sua posição no texto e outras informações.
Se a busca não deu nenhum casamento o objeto Match retornado tem o valor None (nada, ou falso). Isto vai ser útil em nossos programas pois basta um teste com o comando if para decidir logo se a busca teve sucesso.
Um objeto Match tem uma série de métodos. Numa busca simples o método que nos interessa é group(), que retorna o pedaço de texto que foi casado.
Vejamos um exemplo.
In [1]:
import re

# este é o texto onde vamos achar o string 'ana'
texto = 'Ana e Mariana gostam de banana e laranja'

# regex
rgx = 'nana'

# faz a busca
m = re.search(rgx, texto)

# testa se achou algo e imprime a resposta
if m:
    print(m.group())
else:
    print("Nada foi achado")
    
nana
In [6]:
# dá para saber em que posição começa o casamento achado
m.start()
Out[6]:
26
In [7]:
# conferindo...
texto[26:]
Out[7]:
'nana e laranja'
Pode nos interessar encontrar diversas casamentos dentro de um mesmo regex, formando grupos. Só para lembrar, grupos são partes de um regex que ocorrem dentro de parênteses.
Por exemplo, se temos uma data no formato dd/mm/aaaa e queremos encontrar datas num texto, podemos procurá-las com o regex:
rg = '\d{2}/\d{2}/\d{4}'
In [11]:
# um string contendo uma data
txt = 'mercadoria 19/11 comprada em 15/05/2016 foi embarcada em 27/06/2016'

m = re.search('\d{2}/\d{2}/\d{4}', txt)

m.group()
Out[11]:
'15/05/2016'
Mas digamos que estamos interessados no mês e no ano. Para tanto vamos modificar o regex criando um grupo para o mês e outro para o ano:
In [12]:
# novo regex usando dois grupos, um para mês outro para ano
rg = '\d{2}/(\d{2})/(\d{4})'
m1 = re.search(rg, txt)
m1.groups()             # repare no plural, groups e não group
Out[12]:
('05', '2016')
Os diversos grupos num regex podem ser vistos colocando um indice no método group(), lembrando que os grupos são enumerados da esquerda para a direita dentro do regex.
In [16]:
m1.group(1)
Out[16]:
'05'
In [17]:
m1.group(2)
Out[17]:
'2016'

re.split(pattern, string)

Esta função quebra o string removendo todas ocorrências do padrão descrito pelo regex pattern, devolvendo uma lista com os pedaços resultantes.
Por exemplo:
In [2]:
s = 'Ana e Mariana gostam de banana prata'
re.split('ana',s)
Out[2]:
['Ana e Mari', ' gostam de b', 'na prata']

re.findall(pattern, string)

Como vimos a função search() devolve o primeiro pedaço de texto que casa com o padrão. Se quisermos todos os trechos casados usaremos a função findall(), que devolve uma lista de todos casamentos encontrados.
No exemplo acima digamos que queremos todas as datas no texto.
In [13]:
# obtem todas datas no texto
l = re.findall(rg, txt)
l
Out[13]:
[('05', '2016'), ('06', '2016')]
Como se vê obtemos uma lista de tuplas, uma tupla para cada data. Cada tupla por sua vez representa os dois grupos do regex, seu primeiro elemento é o mês e o segundo elemento é o ano.

re.finditer(pattern, string)

A função findall() devolve uma lista com todas ocorrências encontradas. Entretanto às vêzes precisamos saber outras informações sobre cada ocorrência encontrada.
Por exemplo, podemos precisar saber a posição no texto de cada ocorrência do padrão.
A função finditer() oferece estas informações. Esta função cria um iterador interno que devolve um objeto do tipo Match em cada iteração. Este objeto é relativo à próxima ocorrência encontrada e contem informação sobre a posição da ocorrência.
Vejamos um exemplo:
In [6]:
print(s)
rgx = '[A|a]na'                # casa ana ou Ana
for m in re.finditer(rgx, s):
    print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))
Ana e Mariana gostam de banana prata
00-03: Ana
10-13: ana
25-28: ana

re.sub(pattern, repl, string)

Esta função permite fazer substituições num texto. Todas as ocorrências do padrão pattern no string dado serão substituidas pelo texto repl e um string assim modificado é devolvido pela função.
Por exemplo:
In [8]:
print(s)
rg = 'banana'
repl = 'Joana'
s1 = re.sub(rg, repl, s)
print(s1)
Ana e Mariana gostam de banana prata
Ana e Mariana gostam de Joana prata

Exemplo final

Vejamos nosso texto já conhecido, no arquivo 'cienciapolitica.txt'. Para este exemplo você vai precisar deste arquivo, que pode ser baixado aqui. Baixe o arquivo e coloque-o no diretório onde esta este notebook.
A tarefa desejada é encontrar todos advérbios que ocorrem neste texto.
E o que é um advérbio? Bem, ao contrário do idioma inglês, em português os advérbios são coisa complicada de definir. Nesta referência encontramos sete categorias de advérbios, sem contar suas respectivas flexões.
Vamos nos ater aos chamados advérbios de modo, definidos como segue, na referência acima:
Advérbios de Modo
Assim, bem, mal, acinte (de propósito, deliberadamente), adrede (de caso pensado, de propósito, para esse fim), debalde (inutilmente), depressa, devagar, melhor, pior, como, desapontadoramente, generosamente, cuidadosamente, calmamente e muitos outros terminados com o sufixo "mente".
Montar um regex para este padrão é trabalhoso. Vamos assumir que qualquer palavra terminada em mente é um advérbio (peço licença aos puristas e gramáticos, só para um exemplo...).
Como seria um possivel regex?
'[Aa]ssim|bem|mal|acinte|adrede|debalde|depressa|devagar|melhor|pior|como|\w+mente'
Convença-se que cobrimos os casos na definição dada acima...
Vamos ao exemplo
In [10]:
# abra o arquivo dado e leia seu conteudo em um string
with open('cienciapolitica.txt') as f:
    texto = f.read()

# primeiros 1000 caracteres...
print(texto[:1000])

# cria o regex
rgx = '[Aa]ssim|bem|mal|acinte|adrede|debalde|depressa|devagar|melhor|pior|como|\w+mente'

# agora busca todos adverbios com um findall()
re.findall(rgx, texto)
A Ciência Política surgiu como disciplina e instituição em meados do século XIX, período em que avançou como "Ciência do Estado" principalmente na Alemanha, Itália e França. De maneira mais ampla, a Ciência Política pode ser entendida como a disciplina que se volta para o estudo de qualquer fenômeno ligado às estruturas políticas de maneira sistemática, sempre apoiado na observação empírica rigorosa e fundamentado em argumentos racionais. Nesse sentido, a palavra "ciência" é usada como ideia oposta à noção de "opinião", de forma que, como Noberto Bobbio* esclarece em seu Dicionário de Política, "ocupar-se cientificamente de política significa não se abandonar a opiniões e crenças do vulgo, não formular juízos com base em dados imprecisos, mas apoiar-se nas provas dos fatos."

Trata-se, portanto, de uma disciplina das Ciências Sociais que lida com o estudo de sistemas de governo, análises de comportamento político e de atividades políticas em geral. Ela cuida, principalmente, dos atos e
Out[10]:
['como',
 'como',
 'principalmente',
 'como',
 'como',
 'como',
 'cientificamente',
 'principalmente',
 'metodologicamente',
 'como']
Até que o texto está parcimonioso em seu uso de advérbios, não?
E isto termina esta lição.

No comments:

Post a Comment