Conversor recursivo de charset

Fiz para um amigo um script de conversão de charset simplezinho e, como pode ser útil para mais alguém, resolvi abrir o código:

charsetconv

Era mais fácil fazer com shell script, é verdade, mas eu precisava que fosse multiplataforma.

Módulo Python: requests

Esqueça urllib e httplib: Requests resolve do jeito certo.

Você pode instalar via pip com:

pip install requests

Depois, veja como é fácil:

>>> import requests
>>> r=requests.get('http://visie.com.br')
>>> for k,v in r.headers.iteritems():print k,'=>',v
... 
content-length => 7669
content-encoding => gzip
accept-ranges => bytes
expires => Mon, 20 Jan 2014 13:18:30 GMT
vary => Accept-Encoding,Cookie
server => Apache
last-modified => Mon, 20 Jan 2014 12:38:24 GMT
cache-control => max-age=3, must-revalidate
date => Mon, 20 Jan 2014 13:18:27 GMT
content-type => text/html; charset=UTF-8
>>> r.status_code
200
>>> r.reason
'OK'
>>> r.content[:15]
'<!DOCTYPE html>'

Se você precisar fazer uma requisição HTTPS com autenticação e obter o retorno em JSON:

>>> r=requests.get('https://httpbin.org/basic-auth/user/passwd',auth=('user','passwd'))
>>> r.json()
{u'authenticated': True, u'user': u'user'}

Para fazer POST:

>>> r=requests.post('https://httpbin.org/post',data={'foo':'bar'})
>>> r.json()['form']
{u'foo': u'bar'}

Tudo muito, muito simples. E o módulo faz muito mais e está muito bem documentado. Olhe lá.

Porque você deveria aprender várias linguagens de programação

tpp_xlargecover

O programador pragmático não está preso a nenhuma tecnologia.

Há um tempo eu li “The Pragmatic Programmer“, uma espécie de livro de auto-ajuda para programadores. Não é um livro técnico, não vai te ensinar técnicas ou tecnologias, mas fala de um jeito de pensar e traz uma coleção de conselhos daquele tipo que parece óbvio mas ninguém nunca faz.

Um desses conselhos era aprender novas linguagens de programação. Os autores recomendam que você se torne proficiente em uma nova linguagem de programação por ano. O que, eu sei, é muito difícil. Eu não tenho conseguido aprender uma nova linguagem por ano. Mas isso não invalida o conselho de que você deve aprender novas linguagens de programação.

Por que aprender linguagens de programação novas?

Cada linguagem de programação traz consigo um jeito de pensar, um conjunto de soluções e uma comunidade com cultura diferente. E isso enriquece você. O bom programador tem uma visão ampla e não está “preso” a uma única tecnologia.

Por exemplo, digamos que você esteja desenvolvendo um sistema em PHP e precise, de tempos em tempos, fazer parsing de feeds RSS cuja URL foi cadastrada pelos usuários. Digamos que você queira fazer o parsing em segundo plano, a cada meia hora. Claro, você pode usar SimplePie, mas ele não é tão “Simple” assim. Dê uma olhada na documentação. SimplePie é muito poderoso, tem uma porção de recursos, mas vai dar algum trabalho para instalar direito, montar seu script, testar e automatizar.

E se você procurar soluções em outras linguagens de programação? Quanto trabalho vai ter? Vamos ver o que Python tem para nós, usando o excelente módulo feedparser. O módulo feedparser está disponível no gerenciador de pacotes do Ubuntu, e também para instalação via PIP.

import feedparser
print feedparser.parse('http://elcio.com.br/feed')

Mais uma meia dúzia de linhas para ler as URLs e salvar os resultados no banco de dados e pronto, temos um arquivo simples com excelente performance para ser colocado no cron. Bom, falando em performance, esse é o típico processamento que vai rodar muito mais rápido se feito em paralelo. Processamento em paralelo com PHP é um parto, certo? Com Python você pode usar o módulo multiprocessing e fazer isso quase que em uma linha só.

Além de ferramentas, linguagens de programação carregam jeitos de pensar

Uma vez que você já tenha resolvido um problema em uma linguagem de programação, você consegue resolver o mesmo problema em qualquer outra, certo? Bom, quando você aprende uma linguagem nova, você acaba tendo que ler um bocado de código de outras pessoas. E isso te ensina novas soluções para os seus velhos problemas.

Isso é diferente de se aprofundar no estudo de sua linguagem atual. O aprofundamento vai fazer você ler mais códigos das mesmas pessoas, da mesma comunidade, da mesma cultura. Aprender uma linguagem nova faz você ler códigos de gente nova, com outra cultura. Na prática, aprender Python vai fazer você melhor programador PHP, aprender Smalltalk vai fazer você melhor programador Java, etc.

Porque Python

De todas as linguagens de programação que você poderia escolher para começar a aprender algo novo hoje, eu recomendo que, se você ainda não sabe Python, comece por ela. Python roda no Google App Engine, no AWS Elastic Beanstalk e é a linguagem de programação por trás da maioria das ferramentas de administração do Ubuntu. Se você quer construir pequenos scripts para automatizar tarefas em seu servidor, ou quer construir grandes aplicações web para rodar em Cloud, ou se quer criar ferramentas de processamento paralelo massivo, ou interfaces gráficas multiplataforma, Python vai servir.

E é, ao mesmo tempo, uma linguagem que possui recurso fantásticos e vai te ensinar coisas incríveis, e uma das linguagens mais fáceis de aprender que eu conheço.

Que tal começar agora? Leia o excelente “Dive Into Python“, faça os exercícios, depois tente desenvolver alguma coisa na linguagem. E veja se você não vai se tornar mais eficiente em sua linguagem atual.

“Jabá”: Python é a linguagem que escolhemos para a aula de integração multi-linguagem do DevOps Heroes, treinamento da Visie que acontece a partir do dia 27 em São Paulo. Quem estiver lá vai ver isso tudo funcionando na prática. As vagas estão se esgotando.

Pequena dica de Python: módulo webbrowser

O módulo webbrowser permite abrir uma URL no navegador do usuário. Só isso, simples assim. Por exemplo:

import webbrowser
webbrowser.open("http://visie.com.br")

E o site da Visie será aberto no navegador do usuário. Muito útil se você está fazendo um programa para desktop ou mesmo um script para o terminal. No caso de scripts para terminal, se o usuário estiver rodando o script sob uma interface gráfica o site será aberto no navegador padrão dele, já se estiver rodando via ssh ou num outro terminal sem interface gráfica, será usado um navegador de CLI (como o Lynx) se estiver disponível.

Pequena dica de Python: as classes são dinâmicas

Veja:

>>> class Class:
...   def um(self):
...     return 1
... 
>>> c=Class()
>>> c.um()
1
>>> def dois(self):
...   return 2
... 
>>> Class.dois=dois
>>> c.dois()
2
>>> Class.tres=lambda self:3
>>> c.tres()
3
>>> Class.um=lambda self:'Um modificado.'
>>> c.um()
'Um modificado.'

As classes em Python são completamente dinâmicas. Você pode acrescentar métodos quando quiser e isso se refletirá em todos os objetos criados com aquela classe (mais ou menos como o que você faz em Javascript com prototype.)

Pequena dica de Python: leia os fontes

Python é uma linguagem open-source, com uma extensa biblioteca. E os módulos da biblioteca são escritos em Python. Alguns módulos, como pickle e StringIO, tem versões em C mais rápidas (cPickle e cStringIO) mas as versões em Python são mantidas para que você possa ler os fontes.

Veja como descobrir onde estão os fontes de um módulo qualquer:

>>> import glob
>>> glob.__file__
'/usr/lib/python2.7/glob.pyc'

O nome do arquivo tem a extensão pyc. É um compilado. Tire o último “c” e você terá o caminho do arquivo com o código fonte. Dê uma lida no conteúdo do glob.py, por exemplo, e você deve achar muito simples mas bastante interessante.

Se você está aprendendo Python agora, sempre que for resolver uma tarefa sobre determinado assunto, dê uma olhada no código dos módulos relacionados aquele assunto. Você vai ver como a comunidade Python tem abordado problemas semelhantes aos seus e com certeza vai aprender muito.

Pequena dica de Python: string formatting com locals()

Há muitas situações em que uma função faz cálculos e operações para obter valores e, ao final, deve montar uma string com esses valores. Nesta situação, muita gente monta um dicionário com os valores e usa string formatting para gerar a string a ser retornada. Por exemplo:

# -*- encoding: utf-8 -*-
import string,random

template='''Sugestões para %(nome)s:
 - login: %(login)s
 - email: %(email)s
 - site: www.%(domain)s.com
 - senha: %(passwd)s
'''

def sugere_logins(nome):
    login=nome.split(' ')[0].lower()
    domain=nome.split(' ')[-1].lower()
    email=login+'@'+domain+'.com'
    chars=string.letters+string.digits
    passwd=''.join(random.sample(chars,10))
    dados={'login':login,
           'email':email,
           'nome':nome,
           'domain':domain,
           'passwd':passwd}
    print template % dados

Você pode substituir todo o trecho em que monta o dicionário, usando o resultado da função locals(). A função locals() retorna um dicionário com todas as variáveis locais. No exemplo acima, isso ficaria assim:

# -*- encoding: utf-8 -*-
import string,random

template='''Sugestões para %(nome)s:
 - login: %(login)s
 - email: %(email)s
 - site: www.%(domain)s.com
 - senha: %(passwd)s
'''

def sugere_logins(nome):
    login=nome.split(' ')[0].lower()
    domain=nome.split(' ')[-1].lower()
    email=login+'@'+domain+'.com'
    chars=string.letters+string.digits
    passwd=''.join(random.sample(chars,10))
    print template % locals()

Uso muito esse truque ao trabalhar com web2py, fazendo com que os controllers, ao invés de retornar um dicionário montado por mim, retornem locals().

Pequena dica de Python: rot-13

A dica de hoje não é muito útil. Pelo menos eu nunca usei para nada realmente sério. Mas é bem divertida:

>>> t='All your base are belong to us.'
>>> t.encode('rot-13')
'Nyy lbhe onfr ner orybat gb hf.'

Rot-13 é um encoding comum do Python. Você pode escrever código em rot-13 também, se indicar o encoding do arquivo num cabeçalho:

# -*- encoding: rot-13 -*-
vzcbeg fgevat
sbe v va enatr(20):
    cevag fgevat.nfpvv_ybjrepnfr[:v]

Salve o conteúdo acima no arquivo rot13.py e você poderá executar:

$ python rot13.py

Divertido, não?

Pequena dica de Python: mesclando listas

Essa é bem legal. Temos três listas:

>>> a=[1,2,3]
>>> b=[4,5,6]
>>> c=['a','b','c']

Veja:

>>> zip(a,b,c)
[(1, 4, 'a'), (2, 5, 'b'), (3, 6, 'c')]

Simples e útil, não? E se as listas tiverem tamanhos diferentes?

>>> c=['a','b']
>>> zip(a,b,c)
[(1, 4, 'a'), (2, 5, 'b')]

O resultado será truncado para o tamanho da menor lista. Se você quiser, pode obter um comportamento diferente com map, assim:

>>> map(None,a,b,c)
[(1, 4, 'a'), (2, 5, 'b'), (3, 6, None)]

Veja um truque que eu costumava usar bastante com zip:

>>> colors=['Azul','Vermelho','Amarelo','Verde','Branco']
>>> for i in zip(range(len(colors)),colors):
...   print '%i. %s' % i
... 
0. Azul
1. Vermelho
2. Amarelo
3. Verde
4. Branco

Até que eu descobri enumerate():

>>> for i in enumerate(colors):
...   print '%i. %s' % i
... 
0. Azul
1. Vermelho
2. Amarelo
3. Verde
4. Branco

Pequena dica de Python: any e all

Python tem duas funções muito interessantes: any e all. A função any recebe uma lista (ou outro objeto interável) e retorna True se algum dos elementos for avaliado como True. Já all só retorna True se todos os elementos forem avaliados como True. Veja:

>>> everybody=[1,2,3,4]
>>> anybody=[0,1,0,2]
>>> nobody=[0,0,0,0]
>>> any(everybody)
True
>>> any(nobody)
False
>>> any(anybody)
True
>>> all(everybody)
True
>>> all(nobody)
False
>>> all(anybody)
False

Sem segredos, certo? Mas essas duas funções junto com os generators permite uma sintaxe muito interessante:

>>> v=[10,12,25,14]
>>> any(n>20 for n in v)
True
>>> all(n>20 for n in v)
False

Veja um exemplo disso num código real:

if all(v<100 for v in values):
    msg='Para usar seu cupom de desconto, pelo menos '+
        'um dos produtos deve custar mais de R$ 100,00.'

E numa classe real:

class Form:
    # ...
    def validates(self):
        return not any(field.error for field in self.fields)

Módulo Python: gzip

O módulo gzip nos permite ler e escrever em arquivos .gz sem nenhum trabalho. Veja:

>>> import gzip
>>> f=gzip.open('teste.txt.gz','w')
>>> f.write('Hello!\n')
7
>>> f.close()
>>> 
$ gunzip teste.txt.gz 
$ cat teste.txt 
Hello!
$ gzip teste.txt 
$ python
Python 2.7.3 (default, Apr 20 2012, 22:39:59) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import gzip
>>> print gzip.open('teste.txt.gz').read()
Hello!

Pequena dica de Python: __missing__

A partir do Python 2.5, ao estender dicionários, você pode dar a suas classes um método __missing_, que será executado toda vez que você tentar acessar um índice inexistente. Veja:

class Counter(dict):
    def __missing__(self, key):
        self[key]=0
        return self[key]

Veja funcionando:

>>> votos=['john','paul','john','john','ringo',
...        'george','paul','ringo','ringo','john',
...        'paul','john','george','paul','paul',
...        'john','paul','john','ringo','paul']
>>> c=Counter()
>>> for v in votos:
...     c[v]+=1
... 
>>> c
{'ringo': 4, 'paul': 7, 'john': 7, 'george': 2}

Configurando o encoding padrão no Python

Dica: ao iniciar o desenvolvimento com Python em uma máquina nova, procure pelo arquivo sitecustomize.py e acrescente:

import sys
sys.setdefaultencoding('utf-8')

O arquivo sitecustomize.py é automaticamente executado toda vez que você executa o Python (sim, você pode fazer o que quiser nele…) Ele fica em lugares diferentes dependendo da plataforma e da sua instalação do Python. No Ubuntu, fica em /usr/lib/python2.7/ (trocando 2.7 pela versão que você estiver usando.) As linhas acima configuram o encoding padrão como Unicode UTF-8. Claro, você pode configurar outro encoding como o padrão, se preferir.

Recomendo muito que, se você não entende nada de charsets ou nem sabe do que estou falando, use UTF-8.

Dica de Python: iPython

A maioria dos programadores Python gasta bastante tempo no shell interativo. É onde fazemos debugging e testamos novos módulos. É também nossa calculadora (piada interna, quem lê, entenda.) Muitos programadores não conhecem o iPython, uma versão “vitaminada” do shell interativo. No site há versões para download para Windows e em formato de Python-EGG. Para instalá-lo no Ubuntu, você pode usar os repositórios do APT. Use:

sudo apt-get install ipython

Depois disso, é só chamar ipython no terminal ao invés de python quando quiser usar o shell interativo. Mesmo que você não conheça os truques do iPython, já deve achar muito útil usá-lo. Vai ter um formato de output mais fácil de ler que o do shell interativo padrão, prompts coloridos e autocompletar (pressione TAB)

Além disso, há alguns recursos fáceis de usar no iPython que realmente vale a pena conhecer. Experimento digitar, por exemplo:

import glob
glob?

Experimente também:

glob??

O iPython tem muito mais, mas o que já descrevemos aqui deve ser suficiente par quem vai usá-lo pela primeira vez.

Meu ambiente de trabalho em 7 itens

O Mike me convidou, então lá vai:

1. Ubuntu

O sistema operacional que simplesmente funciona. Meu notebook tem Ubuntu, o da minha mulher e os dos meus filhos também. Todos tem o Windows OEM em dual-boot. Nem me lembro quando foi a última vez que vi alguém bootar o Windows lá em casa. Aqui na Visie o Ubuntu também parece ser o sistema predileto de todo mundo que não tem um Mac 😉

Sem brincadeira, se você desenvolve para um sistema Unix-like, deveria usar um. Você vai ter o mesmo modelo de permissões, a mesma estrutura de arquivos e as mesmas ferramentas na sua máquina e na hospedagem. Você vai ter shell script. Um dia desses resolvemos um problema em um projeto criando um link simbólico para um arquivo. Essa solução roda em nossos servidores e em nossos desktops.

2. Git

Ainda encontro muitas empresas por aí que não usam controle de versão. Pode parar de rir, estou falando sério. Eu não entendo como alguém pode escrever software sem um bom sistema de controle de versão distribuído.

3. web2py

O framework de desenvolvimento web mais produtivo que eu já achei.

4. Vim

Vim não é fácil, e deve ser mantido fora do alcance de crianças e animais domésticos. Mas é o editor de código mais rápido do planeta. Extremamente poderoso, indispensável para o bom programador.

5. Firefox e Firebug

O desenvolvimento de HTML, CSS e Javascript se divide em duas eras: antes e depois da Firebug.

6. OpenDNS e Dnsmasq

Nós até conseguimos comprar boas conexões aqui no Brasil. Mas os serviços de DNS de todos os provedores que eu conheço são uma piada.

7. Terminator

Com o Terminator posso dividir uma janela em vários terminais, em abas. Para quem usa vim e muito shell, é uma mão na roda.

E eu vou convidar:

Diego Eis, Ederson Peka, Luciano Motta, Mauro Baraldi, Leandro Lima e Pedro Rogério.

XML não é a resposta 2: parsing

Em meu último post sobre esse assunto, expliquei porque prefiro, na maioria dos casos, usar um formato de descrição de dados como JSON ao invés de XML. Infelizmente, parece que nem todo mundo concorda comigo, e há uma porção de dados úteis disponíveis apenas em XML. O que não é um problema, certo? Do que eu estou reclamando? Os dados estão lá, disponíveis publicamente, muitos de graça, e eu aqui reclamando do formato?

Vamos dar um jeito e ler XML!

A maneira óbvia parece ser usar um parser de XML, e é o que eu faço na maioria dos casos. Toda linguagem e ambiente de desenvolvimento hoje possuem bons parsers de XML para você escolher. Mas nem sempre o parser de XML é a melhor solução.

Vamos tomar como exemplo o retorno do rssWeather.com:

http://www.rssweather.com/wx/br/sao+paulo+aeropor-to/rss.php

A resposta é um RSS com uma tag content:encoded, contendo uma seção CDATA com um trecho de HTML. Você vai ter que fazer duplo parsing se quiser as informações de dentro desse HTML. Ou pode adotar uma solução assim:

import urllib2,re
url='http://www.rssweather.com/wx/br/sao+paulo+aeropor-to/rss.php'
xml=urllib2.urlopen(url).read()
data=dict(re.findall('<dt.*>(.*)</dt><dd.*> ?(.*)</dd>',
        xml.replace(':','').replace('&#176;','\xc2\xb0')))

Cinco linhas de código. Dê uma olhada no retorno disso:

>>> data
{'Barometer': '1028 mb', 'Wind Speed': '10 KMH', 'Dewpoint': '15\xc2\xb0C', 'Wind Direction': 'SSE (160\xc2\xb0)', 'Visibility': '6 km', 'Humidity': '93%', 'Wind Chill': '15\xc2\xb0C', 'Heat Index': '16\xc2\xb0C'}
>>> for i in data.iteritems(): print '%s => %s' % i
... 
Barometer => 1028 mb
Wind Speed => 10 KMH
Dewpoint => 15°C
Wind Direction => SSE (160°)
Visibility => 6 km
Humidity => 93%
Wind Chill => 15°C
Heat Index => 16°C

É importante entender os riscos que estamos assumindo ao adotar essa solução. Se o formato desse HTML for ligeiramente modificado, nosso código pode parar de funcionar. Neste caso, como os dados estão em HTML dentro do XML, isso não é uma desvantagem, porque você teria o mesmo tipo de problema usando um parser SGML.

Será que conseguimos a mesma simplicidade lendo HTML?

import urllib2
url='http://www.bancocentral.gov.br/'
html=urllib2.urlopen(url).read()
dados=html.replace(',','.').split('')[0].split('\r\n')[-7:]
dados=map(float,(dados[0].strip(),dados[3].strip()))

Veja o retorno:

>>> print 'Compra: %.4f, Venda: %.4f' % tuple(dados)
Compra: 1.7784, Venda: 1.7792

XML não é a resposta

Não me entenda mal, XML é uma idéia interessantíssima, pela qual sou apaixonado. Tenho dado aula de XML, escrito HTML como XML válido, publicado e consumido dados em XML, acompanhado as iniciativas de Open Data e RDF no W3C.

O problema é que, enquanto alguns mercados subutilizam XML, tornando o intercâmbio de dados muito complexo, outros exageram no uso. XML não é a panaceia da troca de dados.

Há, por exemplo, uma porção de sistemas que usam XML como arquivos de configuração. Isso torna complicado o trabalho de editar esses XML, e na maioria dos casos torna complicado o parsing disso para rodar o sistema.

Veja um breve exemplo: a busca do Twitter está disponível em três formatos, HTML, ATOM e JSON. Teste a busca a seguir nesses três formatos (veja a diferença nas URLs):

http://search.twitter.com/search?q=%40elcio (HTML)

http://search.twitter.com/search.atom?q=%40elcio (ATOM)

http://search.twitter.com/search.json?q=%40elcio (JSON)

A maneira como o Twitter publica suas buscas é inspiradoras. O formato ATOM servirá para leitores de feeds e para aqueles programadores que adoram escrever parsers XML. E para o resto do mundo, temos JSON. Veja como consumir a URL JSON e obter dados nativos no Python:

import json,urllib2
url='http://search.twitter.com/search.json?q=%40elcio'
dados=json.load(urllib2.urlopen(url))

A dica então é se perguntar se XML é necessário e, se não tiver um bom motivo para usá-lo, trabalhar com JSON, YAML ou, quem sabe, um arquivo de configuração declarando variáveis em sua própria linguagem.

Dica de Python: glob

Um módulo Python pequeno, com uma única função muito simples, mas que você precisa conhecer é o glob. O que ele faz é obter uma listagem de diretório:

import glob
files=glob.glob('*')

O que equivale a:

import os
files=os.listdir('.')

Porém o glob é muito poderoso. Podemos fazer:

glob.glob('*.mad')

Ou:

glob.glob('../pasta/*')

Fica a dica.