Expressões regulares com Python

Esse é um tópico que sempre patinei bastante, ainda tenho alguma dificuldade pra ler e interpretar expressões regulares no python, então estou escrevendo esse artigo principalmente para me ajudar 🙂

O python tem uma biblioteca bem poderosa para expressões regulares:

import re

A função match serve para casar uma string em um texto, por exemplo:

In [2]: re.match("The", "The book is on the table")
Out[2]: <_sre.SRE_Match object; span=(0, 3), match='The'>

Note que a saída é um objeto que tem duas propriedades interessantes, span que devolve onde a string foi encontrada, no caso na posição 0 até 3, e a propriedade match que mostra o que foi encontrado. Mas a função match tem uma limitação, ela só funciona se a string buscada estiver no começo do texto.

Para encontrar strings ao longo do texto devemos usar a função search:

In [8]: re.search("book", "The book is on the table")
Out[8]: <_sre.SRE_Match object; span=(4, 8), match='book'>

Mas o search se limita a retornar apenas uma ocorrência, para encontrar todas as ocorrências da string no texto usamos a função findall:

In [11]: re.findall("book", "The book is on the book table")
Out[11]: ['book', 'book']

Nesse caso ele retornou uma lista com as strings encontradas.

Em expressão regular existem os metacaracteres, são caracteres especiais que usamos para dar match em determinados padrões, por exemplo  o ‘. ‘ (ponto ) , que da match em qualquer caracter, exceto quebra de linha, por exemplo:

In [12]: re.match(".", "The book is on the book table")
Out[12]: <_sre.SRE_Match object; span=(0, 1), match='T'>

In [13]: re.match(".", "12341234")
Out[13]: <_sre.SRE_Match object; span=(0, 1), match='1'>

In [14]: re.match(".", "\t\t\t")
Out[14]: <_sre.SRE_Match object; span=(0, 1), match='\t'>

In [18]: print(re.match(".", "\n\t\t"))
None

Com o search o funcionamento é similar:

In [33]: re.search('.', '\n\t\t')
Out[33]: <_sre.SRE_Match object; span=(1, 2), match='\t'>

Note que nesse caso ele deu match no \t mesmo com o texto começando com \n, o search ignorou o \n e partiu para processar o próximo caractere.

O “.” (ponto) quando usado com  findall tem um comportamento interessante, ele retorna uma lista de cada caractere da sequencia:

In [35]: re.findall(".", "The book")
Out[35]: ['T', 'h', 'e', ' ', 'b', 'o', 'o', 'k']

Outros dois metacaracteres  importantes são os do tipo âncora como o ^ e o $ , eles delimitam o início e o fim da string, por exemplo:

In [39]: re.findall("^.", "The book\n is on the\n book table")
Out[39]: ['T']

Mas é possível pegar os caracteres de todas as linhas do texto:

In [41]: re.findall("^.", "The book\nis on the\nbook table", re.MULTILINE)
Out[41]: ['T', 'i', 'b']

A âncora de fim de linha ( $ ) funciona de forma similar a ^:

In [47]: re.findall(".$", "The book\nis on the\nbook table", re.MULTILINE)
Out[47]: ['k', 'e', 'e']

Podemos combinar as duas âncoras:

In [48]: re.match("^.$", "The")

In [49]: re.match("^.$", "T")
Out[49]: <_sre.SRE_Match object; span=(0, 1), match='T'>

In [50]: re.match("^.$", "")

O “.” é muito útil porém muito abrangente , podemos limitar os caracters buscados, os colchetes “[ ]” funcionam como conjunto, por exemplo:

In [52]: re.findall("[abcdef]", "The book\nis on the\nbook table", re.MULTILINE)
Out[52]: ['e', 'b', 'e', 'b', 'a', 'b', 'e']

Ele entrou dentro do conjunto de caracteres e fez uma busca para cada ocorrência bem sucedida.

Podemos também buscar pelos caracteres que não estão dentro da sequencia declarada dentro dos colchetes:

In [56]: re.findall("[^abcdef]", "The book\n", re.MULTILINE)
Out[56]: ['T', 'h', ' ', 'o', 'o', 'k', '\n']

Existem também os ranges, declaramos eles com o ‘-‘ hifen, por exemplo:

In [57]: re.findall("[a-c]", "The book\n", re.MULTILINE)
Out[57]: ['b']

In [58]: re.findall("[a-h]", "The book\n", re.MULTILINE)
Out[58]: ['h', 'e', 'b']

In [60]: re.findall("[a-zA-Z]", "The book\n", re.MULTILINE)
Out[60]: ['T', 'h', 'e', 'b', 'o', 'o', 'k']

Podemos usar ranges para números e caracteres, segue um exemplo de um range que abrange todos os caracteresque formam palavras:

In [61]: re.findall("[a-zA-Z0-9_]", "The book\n", re.MULTILINE)
Out[61]: ['T', 'h', 'e', 'b', 'o', 'o', 'k']

Esse range é bem útil para filtrar campos de formulário em sites, e por isso usamos bastante esse padrão, por isso existe atalho para abreviar a digitação, o \w :

In [63]: re.findall("\w", "The book @\n", re.MULTILINE)
Out[63]: ['T', 'h', 'e', 'b', 'o', 'o', 'k']

Existem outras abreviaturas para ranges, são elas:

  • \d == [0-9]
  • \D == [^0-9]
  • \s == [\t\n\r\f\v]
  • \S == [^\t\n\r\f\v]
  • \w == [a-zA-Z0-9_]
  • \W == [^a-zA-Z0-9_]

Todas sequências especiais começam com “\”.  Isso gera problemas já que nosso texto pode vir com \ espalhadas no seu conteúdo, para isso temos que escapar a \, por exemplo:

Antes:

olá \n

Correto:

olá \\n

Para evitar dores de cabeça e tornar o código mais legível podemos usar as Raw strings, alertando o python de que aquela string não contem caracteres especiais, por exemplo:

In [88]: re.match(r'\\www', r'\www.otimo.com.gr')
Out[88]: <_sre.SRE_Match object; span=(0, 4), match='\\www'>

Podemos usar o metacaractere  | para expressões do tipo OU:

In [91]: re.match('ww|ss', r'www.otimo.com.gr')
Out[91]: <_sre.SRE_Match object; span=(0, 2), match='ww'>

Expressões regulares também suportam repetições:

In [96]: re.match(r'\w{5}', 'abcdef')
Out[96]: <_sre.SRE_Match object; span=(0, 5), match='abcde'>

In [97]: re.match(r'\w{5}', 'abcdefg')
Out[97]: <_sre.SRE_Match object; span=(0, 5), match='abcde'>

In [98]: re.match(r'\w{5}', 'abcdefg df')
Out[98]: <_sre.SRE_Match object; span=(0, 5), match='abcde'>

In [99]: re.match(r'\w{5}', 'abcd')

In [100]:

Note que ele pegou apenas textos com mais de 4 caracteres, os textos que excederam esse número foram processado mas retornaram apenas o limite de 5 caracteres.

Essa repetição pode ser configurada para retornar os caracteres excedentes, bastando colocar uma vírgula:

In [100]: re.match(r'\w{5,}', 'abcdefg df')
Out[100]: <_sre.SRE_Match object; span=(0, 7), match='abcdefg'>

Note que ele não pegou o “df”
Como você já suspeita, é possível configurar o mínimo e o máximo nas expressões:

In [108]: re.match(r'\w{2,6}', 'abcdefg df')
Out[108]: <_sre.SRE_Match object; span=(0, 6), match='abcdef'>

O metacaractere “?” é usado de duas formas, para limitar o mínimo de repetições (equivalente a {,1}) e para transformar expressões greed em expressões  lazy, por exemplo:

# Transformando a expressão greed em lazy
In [111]: re.match(r'\w{2,}', 'abcdefg df')
Out[111]: <_sre.SRE_Match object; span=(0, 7), match='abcdefg'>

In [112]: re.match(r'\w{2,}?', 'abcdefg df')
Out[112]: <_sre.SRE_Match object; span=(0, 2), match='ab'>

Já o metacaractere “*” é usado para pegar zero ou mais ocorrências ( equivalente a {,} ) :

In [120]: re.match(r'\w', 'abcdefg df')
 Out[120]: <_sre.SRE_Match object; span=(0, 1), match='a'>

In [121]: re.match(r'\w*', 'abcdefg df')
 Out[121]: <_sre.SRE_Match object; span=(0, 7), match='abcdefg'>

O metacaractere “+” é usado para pegar 1 ou mais ocorrências  ( equivale a { 1 , } ):

In [128]: re.match(r'\w+', '')

In [129]: re.match(r'\w+', 'abcdefg df')
Out[129]: <_sre.SRE_Match object; span=(0, 7), match='abcdefg'>

Um exemplo prático do uso do caractere “?” em uma  expressão:

In [132]: re.findall(r'".+"?', 'src="blablabla" alt="altaltalt"')
Out[132]: ['"blablabla" alt="altaltalt"']

Note que queriamos apenas os valores dos atributos de forma separada, mas como o “+” é greedy ele trouxe o resto da expressão, para resolver:

In [133]: re.findall(r'".+?"', 'src="bla bla bla" alt="altaltalt"')
Out[133]: ['"bla bla bla"', '"altaltalt"']

Para buscar valores vazios nos atributos temos que substituir o + por * :

In [134]: re.findall(r'".+?"', 'src="" alt=""')
Out[134]: ['"" alt="']

In [135]: re.findall(r'".*?"', 'src="" alt=""')
Out[135]: ['""', '""']

Um exemplo fazendo parsing de uma tag HTML:

In [136]: html = '<input type="email" id="id_email" name="user mail">'

In [137]: padrao = r'<(.+?) type="(.+?)" id="(.+?)" name="(.+?)"'

In [138]: re.match(padrao, html).groups()
Out[138]: ('input', 'email', 'id_email', 'user mail')

É possível criar um dicionário com os resultados da expressão regular:

In [143]: padrao = r'<(?P<tag>.+?) (?:(?:type="(?P<type>.+?)"|id="(?P<id>.+?)"|name="(P<name>.+?)") ?)*'

In [144]: re.match(padrao, html).groups()
Out[144]: ('input', 'email', 'id_email', None)

In [145]: re.match(padrao, html).groupdict()
Out[145]: {'id': 'id_email', 'tag': 'input', 'type': 'email'}

Espero que seja útil 😀

Inspiração:  http://henriquebastos.net/

Problema com PyEnv no OSX Sierra

Pyenv é um gerenciador de instalações do Python, permite a instalação de várias versões do interpretador em paralelo, incluindo pypy, jython stackless etc.

Após instalar o OSX Sierra tive um problema que me impedia de instalar o python 3.5.2:

zipimport.ZipImportError: can't decompress data; zlib not available

Por alguma razão, no Sierra a biblioteca zlib não vem instalado por padrão para uso no stack unix padrão, para resolver isso fiz o seguinte:

xcode-select --install

Com isso uma caixa de diálogo vai se oferecer para instalar o XCode inteiro ou apenas as ferramentas de console. Instalei as ferramentas de console e o problema foi resolvido 🙂

Como lidar com settings.py local vs produção no django

Não é legal colocar no github suas senhas ou configurações especificas da sua máquina de desenvolvimento, e no Django padrão ele praticamente te induz ao erro.

Mas não se preocupe, é bem simples adaptar seu projeto de tal forma que suas configurações locais não se misturem com o código que vai ser publicado. basta adicionar as seguintes linhas no fim do seu arquivo settings.py:

try:
   from local_settings import *
except ImportError, e:
   pass

Agora vc pode editar o local_settings.py de tal forma a ‘re-escrever’ variaveis, listas, tuplas do settings.py do seu projeto. Só não esqueça de manter o local_settings.py fora do seu repositório !

Customizando o prompt interativo do python

Em algumas distros linux notei que o interpretador padrão do Python, aquele invocado pelo comando python no terminal, possuiam autocomplete e histórico. Eu sei que existe o ipython o bpython, mas em várias situações onde eles não estão disponíveis, o interpretador interativo padrão é a melhor solução.

Existe uma variavel de ambiente chamada PYTHONSTARTUP, que guarda o path do seu script de inicialização, por exemplo:

export PYTHONSTARTUP="~/.pythonstartup"

O conteúdo de ~/.pythonstartup pode ser customizando à vontade, e ainda existe uma documentação básica sobre o assunto aqui.

Um exemplo de pythonstartup :

import readline
import rlcompleter
import atexit
import os
from datetime import datetime as d

readline.parse_and_bind('tab: complete')

histfile = os.path.join(os.environ['HOME'], '.pythonhistory')

try:
    readline.read_history_file(histfile)
except IOError:
    pass

atexit.register(readline.write_history_file, histfile)

def isodate():
    return d.now().isoformat()

del os, histfile, readline, rlcompleter

Como você pode ver é possível adicionar funções e objetos personalizados para tornar seu prompt mais flexivel.

O dia em que o Glade parou, um passeio pelos RADs Opensource

A um ano atras fiz um projeto para o CCJ que envolvia a criação de um sistema de gerenciamento de telecentros, uma versão simplificada de um programa para gerenciar Lan-Houses chamdo Pylan.

O Pylan foi feito em python e gtk, e o primeiro protótipo funcional ficou pronto em 3 semanas, bem rápido, mas como não usei uma ferramenta de RAD para desenhar a interface posso assegurar que pelo menos metade do tempo de coding foi tentando ajustar a interface na mão, principalmente os formulários. Como o projeto tinha tudo para ser um oneshot, isso não era um problema tão grande, já que no futuro poucas mudanças na interface seriam necessárias.

Mas hoje eu vivo uma situação curiosa, apesar de ter minha empresa aberta legalizada e plenamente funcional, sou empregado em tempo integral em outra .A Hyddro foi concebida para ser uma empresa de verdade, e não uma passadora de notas, ela surgiu num vácuo de negociações que surgiu no processo da minha integração ao grupo da empresa em que sou empregado por circunstâncias obtusas, falhas de comunicação, desconfianças e conflito de interesses que são papo de bar, e não pra blog. Ou seja, toco minha empresa fora do horário comercial, e isso cansa, cansa muito e portanto surgiu a necessidade de usar RAD no produtos da empresa.

Surgiram clientes em potencial para uma versão atualizada do Pylan, que bancariam seu desenvolvimento por alguns meses, e um outro cliente que abriria a portas para um novo mercado, o das lan houses e cyber cafes com linux. Um mercado cheio de soluções pela metade, ruins ou emuladas via dosbox, mas que tem um potêncial muito grande se bem trabalhado.

Voltando, ferramentas de RAD permitem desenhar as interfaces dos programas de forma independente do código, tudo clicando e arrastando, depois é só escrever as classes e os métodos para se conecetar com a interface, reduzindo drásticamente o tempo de desenvolvimento, o que pra mim é fundamental já que não vou conseguit manter um emprego integral e tocar meu negócio sem isso…

No linux existem algumas ferramentas desse tipo:

  • Netbeans possui uma ferramenta de RAD integrada ao IDE, para java.
  • QTDesigner, cria arquivos .ui , que podem ser aproveitados por programas feitos em C++, python, ruby, java, C#  usando QT.
  • Glade, cria arquivos xml para biblioteca GTK, para liguagem C/C++, python, ruby, java etc…
  • wxGlade, cria código em python, c++, perl , lisp ou um xml que pode ser importado pela aplicação.
  • Mono, cria interfaces para qualquer liguagem integrada ao CLR/.NET, C#, IronPython, IronRuby etc…

Usei várias madrugadas para desenvolver protótipos em cada uma dessas soluções, considerando minhas necessidades:

  • Multiplataforma
  • Desempenho decente em Windows e Mac
  • Visualmente bem integrado com a plataforma que está rodando
  • Fácil de instalar, sem muitas dependências
  • Velocidade no desenvolvimento
  • Desenvolvimento Ágil

Mono


O primeiro protótipo que desenvolvi foi em Mono, e digo que foi a experiência mais fantástica em desenvolvimento desktop que já tive !

O monodevelop, não fica no seu caminho, ele é realmente útil, simples de usar e intuitivo, C# é bem parecido com Java, possui bibliotecas bem resolvidas e uma boa compatibilidade com bibliotecas livres desenvolvidas para .NET (sim isso existe).

É Super multiplataforma, rodou bem rápido no Linux, Windows e Mac, se integrou visualmente muito bem no Linux e no Windows… no Mac ficou bem estranho. O ubuntu já vem com mono instalado, no windows tive que instalar o GTK#, no mac tive que instalar o mono e o GTK#, mas em todos os casos foi bem tranquila as instações e o tempo de download das bibliotecas.

Existem bibliotecas como a NUnit para testes, e bons depuradores e profilers integrados no monodevelop.

Levei pouco mais que 4 horas para terminar o protótipo, simplesmente fantástico.

Mas o mono tem um problema sério, o projeto é constantemente trolado pela microsoft e pela comunidade opensource, só pra se ter uma idéia existem milhares de tutoriais e scripts para tornar sua distro mono-free. Tem até distro que seu único diferencial é não ter mono !

Netbeans

No netbeans tive uma experiência de produtividade parecida com a do Mono, com a vantagem de usar swing, não tive que instalar nada na hora do deploy, bastou apenas executar o Jar. O Swing integra nativamente o ‘look’ da aplicação, mas não o ‘feel’, o programa ficou estranho em todos os sistemas, e ficou lento … muito lento. Lendo a respeito, a galera diz que o swing não recebe atenção dos desenvolvedores do java a pelo menos 5 anos, deve ser verdade.

Por mais que eu tenha gostado o desempenho e a pobreza de widgets o tirou da jogada.

QTDesigner

A qualidade gráfica e a riqueza de widgets é fenomenal, outstanding, mas todo seu potencial só é atingido usando C++, eu queria muito usar python, não que não seja possível, mas tenho minhas dúvidas já que o pyQT é desenvolvido por uma empresa independente da nokia.

Outra coisa que pega é que o PyQT exige pagamento de licença no caso de softwares comerciais, o software é livre, mas vou receber pra melhora-lo, tenho muitas dúvidas.

WXGlade

Bem rápido de desenvolver, tem muitos widgets legais, é comparavel como  QT nesse ponto, a portabilidade é fantástica, basta instalar um pacotinho e o sistema funciona como se fosse nativo. É uma ótima opção, muito bem documentado, mas possui bugs e isso é um tanto preocupante.

Glade

Foi minha primeira opção, a ferramenta é muito boa, da pra integrar com o pygtk tranquilamente. O grande problema é que para rodar pygtk no windows é necessária a instalação de meia dúzia de pacotes, além do python claro, o que incha o sistema. No Mac roda sob o X11 o que não é legal. Apesar de tudo, ele é melhor integrado com o GNOME e tem uma qualidade superior aos outros, o Pylan é feito em PyGTK e funciona muito bem.

Meu drama é que após redesenhar toda interface do pylan no glade o programa travou, e não consegue mais abrir o xml gerado… Fiquei na mão, abri um bug report, mas perdi a confiança no sistema.

Agora estou aqui, dividido, com uma deadline, estou bastante inclinado a usar WX com python ou QT com C++, e agora ?

N truques em python

python -m SimpleHTTPServer  # cria servidor http na porta 8000

python -m smtpd -n -c DebuggingServer localhost:1025 # cria servidor smtp na porta 1025
a, b = b, a  # troca os valores de a e b sem precisar de variavel auxiliar

x = "Alguma coisa"
y = x[::-1] #  Inverte a string
import antigravity # só no python 3

e por ai vai ...