Monday, November 9, 2009

Como usar bpython com Django

O bpython é um espetacular console interativo Python com auto-completar visual e help. Funciona no Linux, Mac OSX e Unixes em geral, mas não no Windows porque depende do módulo curses.

Os screenshots não fazem justiça ao programa, porque nele tudo é dinâmico, então você precisa mesmo experimentar para sacar. Além disso ele é mais bonito no Linux que no Mac OSX porque as linhas verticais aparecem contínuas.

Para quem usa Windows, o IPython também é legal, e vale muito a pena usá-lo com o Django.

Mas para quem pode usar o bpython eis como fazer.



Método 1: ler manualmente os settings do django

1) Inicie a sessão do bpython a partir do diretório principal do seu projeto Django (onde fica o arquivo settings.py).

2) Digite no prompt >>>

from django.core.management import setup_environ
import settings
setup_environ(settings)


A partir desse ponto, você pode interagir normalmente com os modelos e outros objetos da sua aplicação Django.



Metodo 2: rodar um script que lê os settings, e entrar em modo interativo


1) crie um arquivo (digamos, .dbpython.py) com o conteúdo:

from django.core.management import setup_environ
import settings
setup_environ(settings)
from minha_app.models import * # opcional


Note que a quarta linha é opcional, e você precisa alterar o nome da app, e repetir essa linha para cada app que quiser acessar com mais comodidade.

2) execute o script usando o bpython com a opção -i (que faz entrar em modo interativo, como no python normal):

$ bpython -i dbpython.py


3) Bônus: para não ter que digitar o comando acima toda vez, crie um script de shell:

#!/bin/sh
# este eh o arquivo dbp.sh
bpython -i dbpython.py


Depois torne este script executável:

$ chmod +x dbp.sh


Agora basta digitar:

$ ./dbp.sh


E pronto, as maravilhas do bpython!



Metodo 3: configurar um script de inicialização para o bpython

Este é o método documentado no site do bpython:

http://docs.bpython-interpreter.org/django.html

Eu estou usando o método 2 porque gostei de ter um script dbpython.py customizado para o meu projeto atual, assim eu faço os imports dos models das apps que estou desenvolvendo.

Claro que seria possível sem muito esforço alterar o dbpython.py para fazer os imports dos models automaticamente, lendo o atributo settings.INSTALLED_APPS, mas isso fica como um exercício para o leitor.

Agradeço ao meu colega de trabalho, Fabio Montefuscolo, pela dica que deu início a este post.

Wednesday, August 26, 2009

Construção civil e engenharia de software

A construção de uma casa nos EUA é muito diferente da construção de uma casa no Brasil. Aqui a casa comum é construída tijolo por tijolo, azulejo por azulejo, telha por telha. Numa economia mais desenvolvida, os custos de mão de obra tornam inviável esse modo de construção artesanal.

A casa típica da classe média americana não é propriamente construída, e sim montada. Apenas a construção da fundação se parece com o que se faz no Brasil. Sobre a fundação, a casa gringa é montada a partir de elementos estruturais, paredes, pisos e coberturas que chegam prontos ao local da construção, como um kit. As peças em sua maioria já vem até cortadas nos tamanhos e formatos certos. Os banheiros, que são a parte mais complicada de qualquer casa, costumam ser entregues prontos: chegam pré-montados em uma carreta e são colocados no lugar por um guindaste.

Em outras palavras, os americanos montam casas mais ou menos como se montam automóveis nos EUA, no Brasil ou em qualquer lugar. Na Ford por exemplo, desde os tempos do Modelo T, motores, painéis e bancos não são construídos na linha de montagem, mas apenas integrados. Ou seja, a prática da construção civil americana já é industrial, enquanto que a brasileira ainda é artesanal.

Na engenharia de software o sonho que vem impulsionando a adoção da Orientação a Objetos é o uso de componentes pré-montados, permitindo a redução do tempo de desenvolvimento e o aumento da qualidade. Quando as aplicações são feitas artesanalmente, a produtividade é muito baixa. Esse modelo só se sustenta quando a remuneração dos programadores também é baixa. Vira um ciclo vicioso: o cara ganha pouco porque trabalha mal, e trabalha mal porque ganha pouco. Como ganha pouco tem que trabalhar muito, e aí não tem tempo para estudar.

O uso de frameworks vai além: oferece um conjunto de componentes já integrado, e o papel do programador passa a ser implementar componentes específicos que se conectam de forma bem definida aos demais. O framework é como o kit estrutural de uma casa.

Usar um framework exige um investimento maior do tempo programador até ele entender como as partes se encaixam e atingir um bom nível de produtividade. Não dá para simplesmente sair programando do jeito que você quer, é preciso estudar antes, às vezes bastante, para entender como programar componentes que vão se encaixar num framework. Mas uma vez superada esta fase, é possível entrar em um ciclo virtuoso: você ganha bem porque produz bastante e com qualidade, e ganhando bem pode investir mais na própria formação e atualização.

Por sinal, uma das melhores formas que eu conheço de investir em mim mesmo é frequentar congressos de alta qualidade como a Python Brasil.

Sunday, August 2, 2009

Unibabel: bricando com Unicode

Este fim-de-semana eu comecei um novo projeto experimental, o Unibabel.

É uma interface Web para explorar a Unicode database que vem embutida na linguagem Python. Eu me divirto passeando entre as páginas e encontrando caracteres, ideogramas e símbolos elegantes, exóticos, familiares ou bizarros.

Veja por exemplo esta página de operadores matemáticos, e esta letra do alfabeto malaio. Link
Serve também como um exemplo bem básico de como fazer rodar uma aplicação Django no Google App Engine.

O código do Unibabel está publicado:

http://code.google.com/p/unibabel/source/browse/unibabel/views.py


Por enquanto o Unibabel não usa o datastore do Google, todos os dados vêm do módulo unicodedata do Python. Futuramente vou usar o datastore para permitir buscas por atributos dos caracteres (por exemplo, listar todos os caracteres numéricos, todos os símbolos, ou todas as variantes da letra 'A'...).

Se alguém quiser colaborar, fique à vontade para clonar o repositório e se fizer algo interessante me manda uma mensagem que eu posso incorporar a sua contribuição.

Tuesday, June 2, 2009

O diabo da acentuação (2)

Com a explosão da Internet, ficou sem sentido continuar usando sistemas de codificação de caracteres diferentes em cada parte do mundo. Mas para unificar, seria preciso uma codificação que não ia caber em um byte. Então a primeira tentativa foi usar dois bytes, o que permite códigos de \x0000 a \xFFFF, ou seja, 65536 caracteres distintos. Era o que propunham as primeiras versões do padrão Unicode. E assim várias linguagens, inclusive Java, adotaram uma representação interna de strings onde cada caractere equivale a dois bytes.

Outra idéia errada: 1 caractere == 2 bytes



Eu aprendi o que era Unicode estudando Java, e sempre achei normal que um caractere Unicode fosse representado por dois bytes em Java, numa codificação chamada UCS2. Até que em 2006 eu comecei a estudar a linguagem Ruby, e constatei que o suporte a Unicode em Ruby era mais primitivo que em Python. Fiquei surpreso porque o criador de Ruby, Yukihiro Matsumoto, é japonês, e a linguagem é muito popular no Japão.

Mas a propaganda aqui no ocidente é que o Unicode veio para resolver o problema das línguas orientais, então porque faltava um bom suporte em Ruby? Quem pesquisar um pouco o assunto vai descobrir que as primeiras versões do Unicode foram rejeitadas pelo chineses, japoneses e coreanos! Os principais motivos foram (1) a unificação de certos ideogramas que embora visualmente parecidos queriam dizer coisas diferentes e (2) a falta de uma especificação de como estender a codificação para além dos 2 bytes. O segundo ponto é interessante: quando se usa um alfabeto, uma nova palavra é apenas um novo arranjo das mesmas letras; mas em chinês, uma nova palavra pode ser um novo ideograma.

Para resolver este problema, é preciso abstrair um pouco mais...

Unicode é uma tabela de codepoints



O foco do padrão Unicode não é estabelecer uma relação entre caracteres e bytes, e sim uma relação entre caracteres e códigos numéricos chamados codepoints. Como estes codepoints serão representados na memória ou em um arquivo é uma questão secundária, até porque diferentes aplicações vão exigir diferentes representações: um formato bom para transmitir pode ser ruim para processar, por exemplo.

Na documentação do Unicode, os codepoints são identificados por números hexadecimais com o prefixo 'U+'. Veja uma pequena amostra de codepoints e caracteres:

U+6C23 氣 CJK UNIFIED IDEOGRAPH-6C23

U+06BF ڿ ARABIC LETTER TCHEH WITH DOT ABOVE

U+2620 ☠ SKULL AND CROSSBONES

U+0D0B ഋ MALAYALAM LETTER VOCALIC R

U+4DF1 ䷱ HEXAGRAM FOR THE CAULDRON

O texto eu caixa alta em cada linha acima é o atributo "name" do caractere, segundo a tabela Unicode. Além de letras de vários idiomas, o Unicode inclui também símbolos matemáticos, naipes do baralho e até hexagramas do I-Ching.

Vale a pena explorar o site oficial do Unicode, em particular as code charts (tabelas de código) onde os milhares de caracteres aparecem agrupados por idioma ou assunto.

O diabo da acentuação

Estamos no meio de uma imensa migração para Unicode, aquela terra prometida onde caracteres acentuados são cidadãos de primeira classe, e até o conceito da energia interior qi pode ser representado na forma original: 氣.

Enquanto não chegamos lá, reina a confusão na terra da acentuação. Em fóruns de programadores a gente vê muita gente perdida, procurando e oferecendo soluções baseadas mais em superstição e mitos do que em razão e fatos.

Este é o primeiro de uma série de posts para tentar esclarecer essa bagunça.

codificação: neste assunto, codicação não tem nada a ver com cifras ou código-fonte. Uma codificação é apenas uma forma de representar caracteres através de códigos numéricos, para armazenagem em computadores digitais.


A idéia errada: 1 caractere == 1 byte



A computação nasceu em países que falam inglês, um idioma onde se usam apenas as 26 letras de A a Z, sem acentos nem cedilhas [1]. Além disso, a memória das máquinas era muito limitada, então depois de alguma briga entre fabricantes americanos e usuários europeus se estabeleceu a idéia equivocada de que um byte corresponde a um caractere.

Essa idéia simplista gerou codificações como esta (clique na figura para ampliar):



Esta figura na verdade representa uma série de gambiarras, uma em cima da outra. Para começar, a faixa preta representa caracteres projetados para controlar um teletipo, um equipamento mais obsoleto até que um mimeógrafo. Por exemplo, o caractere '\x0C' [2], serve para avançar uma folha no papel e o '\x07' é o BELL, faz o teletipo tocar um sino para chamar a atenção do operador!

Apenas três destes caracteres de controle são largamente utilizados hoje: o HORIZONTAL TAB ('\x09'), o LINE FEED ('\x0a') e o CARRIAGE RETURN ('\x0d'). Os 32 caracteres de controle, bem como a faixa azul, formam o padrão ASCII (pronuncia-se ásqui, e não ásqui-2), totalizando 128 caracteres.

A faixa verde é a tabela ISO-8859-1, também conhecida como Latin-1, uma das várias tabelas de caracteres criadas na norma ISO-8859 para padronizar conjuntos de caracteres alfabéticos. Na tabela Latin-1 aparecem todos os caracteres usados nos idiomas da Europa ocidental. A mesma norma ISO inclui outras tabelas, como a ISO-8859-2 (Latin-2), para idiomas da Europa oriental (como Polonês, Tcheco e Húngaro) e a ISO-8859-5 que contém letras do alfabeto cirílico usado na Russia, Bulgaria, Sérvia etc. [3]

As duas fileiras vermelhas são um "puxadinho" feito pela Microsoft. A norma ISO-8859 reservava estas 32 posições para ainda mais caracteres de controle, que nunca "pegaram", então a Microsoft inventou um padrão chamado "CP-1252" [4] que é basicamente a combinação de ASCII com Latin-1 e mais 27 símbolos como o € (euro) o • (bullet) e as aspas assimétricas “assim” que tanto atrapalham a nossa vida de programadores.

Muitos aplicativos, especialmente no mundo Windows, alegam usar a codificação ISO-8859-1 mas na verdade usam CP-1252, violando a lei de Postel [5]. Isso causa alguma confusão porque se você tenta ler um arquivo CP-1252 como se fosse ISO-8859-1, seu programa não vai saber o que fazer com os €, •, “aspas” etc. Mas o inverso não causa problemas: você sempre pode ler um ISO-8859-1 como se fosse CP-1252, porque o primeiro é um sub-conjunto do segundo.

E o 氣?



Realmente não tem espaço naquela tabela para o caractere chinês do "qi" e os outros milhares de caracteres chineses, japoneses, coreanos, ou para dezenas de idiomas que não usam o alfabeto latino, como árabe, hebraico, tailandês e sânscrito. Essa constatação acabou com o princípio de que "1 byte == 1 caractere", e nos conduziu ao Unicode, tema do próximo post.




[1] na verdade, isso já é uma simplificação; qualquer dicionário de inglês tem a palavra façade (fachada), escrita assim mesmo, com cedilha; a palavra virou até um termo técnico em engenharia de software, pois é o nome de um dos padrões de projeto originais.


[2] \x0C é hexadecimal, em decimal seria 12


[3] a ISO-8859-1 (Latin-1) continua importante até hoje, mas a ISO-8859-5 "não pegou". Os russos preferem outros padrões, como KOI-7 ou KOI-8.


[4] conhecido também como "codepage 1252" ou oficialmente "Windows-1252"


[5] "Seja liberal no que aceita conservador no que envia". Procure "Postel Law" no Google e aprenda um princípio realmente importante e útil para a engenharia e para a vida em sociedade.

Monday, April 13, 2009

Python e o caminho do meio

A maneira como Python trata variáveis faz muito sentido, e exemplifica uma característica que eu gosto muito na linguagem: a opção pelo caminho do meio.

A maioria das linguagens segue um de dois caminhos extremos no tratamento de variáveis.

Um dos caminhos é aquele que exige que cada variável seja declarada, como ocorre em C, Pascal, Java etc. O curioso é que várias linguagens obrigam o programador a declarar uma variável, mas nem todas elas obrigam o programador a inicializar a variável antes de usá-la, o que gera bugs que às vezes são difíceis de achar.

Ou seja, tornar obrigatória a declaração da variável é algo que facilita a vida do compilador, mas não necessariamente resolve o problema do programador. Claro que os compiladores modernos reclamam quando encontram variáveis que nunca foram inicializadas em expressões, mas isso é um remendo.

O outro caminho, radical na direção oposta, é o das linguagens que não obrigam a declaração da variável e nem a sua inicialização. É uma tentativa equivocada de simplificar a vida do programador. Linguagens que seguem essa linha são JavaScript, PHP, Perl, Basic e outras chamadas "linguagens de scripting". Em algumas delas (Perl, VisualBasic) você pode mandar o interpretador reclamar quando encontra uma variável não declarada, mas esta opção não costuma ser o default. Em alguns casos a variável não inicializada tem um valor especial como "undefined" ou "null".

Nestas linguagens, a ocorrência de uma variável não inicializada em uma expressão geralmente faz com que ela seja avaliada com algum valor default, tipo 0 (zero) se for uma expressão numérica ou "" (string vazia) etc. Tudo isso faz com que ocorram bugs muito chatos de encontrar, porque se o nome de uma variável é soletrado incorretamente, o programador não é avisado e um valor sem sentido é introduzido no programa. Em projetos grandes, isso se torna um problema muito grave, e é um dos motivos pelos quais os adeptos de abordagens mais rigorosas consideram que as linguagens de scripting não são "sérias".

Python escolheu o caminho do meio, o mais sensato na minha opinião. Não é preciso declarar variáveis, até porque tal declaração não teria muita utilidade em uma linguagem de tipagem dinâmica onde o tipo está vinculado ao objeto, e não à variável. Mas Python exige que você inicialize uma variável antes de usá-la. Veja:

>>> b = a + 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined


Então isso obriga você a escrever:

>>> a = 3
>>> b = a + 1
>>> print a, b
3 4


Note que como toda variável precisa obrigatoriamente ser inicializada, isso é mais útil do que a exigência de que ela seja simplesmente declarada. Porque evita valores inválidos, e o momento da atribuição acaba funcionando como uma declaração também.

O modo como Python lida com identificadores, não só neste aspecto, mas também no seu sofisticado sistema de namespaces, evita conflitos de nomes e é um dos fatores que torna Python atraente para projetos de larga escala. Ruby adota a mesma filosofia de Python quanto à inicialização de variáveis, mas não tem o suporte a namespaces.

Este é apenas mais um exemplo de como Python é uma linguagem "simples e correta", como definiu meu amigo Geraldo Coen.

Thursday, April 2, 2009

Python vai migrar para Mercurial. Uebaaa!!!!

Perdõem a falta de objetividade no título, mas eu adorei esta decisão dos core developers da linguagem Python: entre os principais sistemas de controle de versão distribuídos, o Guido van Rossum optou pelo Mercurial, também conhecido como Hg, seu símbolo na tabela periódica.

No primeiro dia da conferência o Brett Cannon (perfil no Ohloh) fez uma palestra relâmpago explicando que os desenvolvedores do Python haviam decidido migrar do sistema de controle de versão atual, o Subversion, para um DVCS (sistema distribuído de controle de versões) e que ultimamente três sistemas tinham sido avaliados: Bazaar, Mercurial e Git.

Na mesma palestra-relâmpago, Brett disse que o Git havia sido descartado por ser o menos popular entre os principais desenvolvedores da linguagem Python e por não ser adequadamente suportado no Windows. Também pesou, mas não foi decisivo, o fato de que o Mercurial e o Bazaar são escritos em Python com partes em C, mas o Git é praticamente C puro com alguma coisa escrita em Perl. O vídeo da primeira sessão de palestras relâmpago, incluindo esta do Brett Cannon, já está no Blip.tv.

Chegando no Hg

Uma vez descartado o Git, foram apenas mais dois dias de suspense até que o Guido anunciou sua decisão favorável ao Hg, baseada principalmente nas preferências pessoais dos core developers que lhe enviaram e-mails ou twittaram sobre o tema. Segundo ele, o Git tem muitos fãs mas também um alto índice de rejeição, e o Hg tem ainda mais fãs que o Bazaar, com menos oposição. Então é isso, Mercurial na cabeça!

Gostei muito dessa escolha, porque passei bastante tempo tentando me acostumar com a idéia de usar um DVCS em vez do Subversion. Inicialmente brinquei com o Bazaar, mas só entendi realmente a idéia quando experimentei o Mercurial. Não sei se é a documentação dele que é melhor, ou seus comandos fazem mais sentido para mim, mas o fato é que com o manual do Mercurial a coisa toda finalmente fez sentido para mim.

Na verdade, eu vou mais longe, acho que aprender a usar o Mercurial é mais fácil que aprender a usar o Subversion, então se você ainda não usa um sistema de controle de versões, chegou a hora de começar!

Git e eu

Depois que eu já havia adotado o Mercurial eu resolvi aprender o Git porque estava na moda, e muita gente que eu respeito falava muito bem dele. Achei a documentação do Git horrenda, e suas mensagens de erro estão entre as piores que eu já vi. Ele foi desenvolvido pelo Linus e outros kernel hackers para uso próprio, e a documentação deixa claro que eles não se importam minimamente se meros mortais vão usar a ferramenta. Daí a comunidade Rails adotou o Git e com seu talento evangelista transformaram o Git em mais um ritual de sua religião.

Acontece que a maioria dos defensores do Git que eu conheço nunca experimentaram o Mercurial, e quase todas as vantagens do Git que eles apregoam por aí se aplicam perfeitamente ao Mercurial e até ao Bazaar.

Para mim as únicas vantagens objetivas do Git sobre o Mercurial são o desempenho e o Github.

A vantagem do desempenho é relativa, porque a gente não gerencia milhões de linhas de código como o Linus e os kernel hackers, então na prática isso significa que o Git faz em 0.01s algo que o Hg leva 0.1s para fazer.

Só que esta vantagem é anulada pelas armadilhas anti-usuário do Git. Cada vez que você perde 20 minutos tentando desfazer um erro lutando contra a interface hostil do Git, lá se vão 1200 segundos, ou seja, você acabou de perder a vantagem de desempenho equivalente a milhares de operações!

O Github é muito legal pelo seu componente social (virou uma espécie de Orkut de programadores), e continua sendo a melhor razão objetiva para usar o Git.

Agora com essa decisão da comunidade Python, o Bitbucket.org deve ficar mais popular, e funcionalmente ele é bem parecido com o Github.

Tentei muito gostar do Git, mas estou muito contente por poder deixá-lo de lado e voltar a me concentrar no Mercurial.

Na órbita de Mercurial

Vale notar que o Hg também foi adotado pelos core developers da linguagem Java e do projeto Mozilla (Firefox), então estamos na companhia de mega projetos (lista de usuários do Hg).

Com a adesão da comunidade Python ao Hg, vou migrar meus projetos relacionados a Python do Github para o Bitbucket.org. Nos vemos lá!

Wednesday, March 25, 2009

Django no mundo real

Esse foi o tema do primeiro tutorial que eu assisti na PyCon 2009 em Chicago. A conferência começa dia 27 mas nos dois dias antes estão rolando mais de 30 tutoriais.

Este tutorial na verdade foi uma palestra longa, não foi no estilo "mão na massa". Os apresentadores foram o Jacob Kaplan-Moss, um dos criadores do Django e co-autor do Django Book, e o James Bennett, autor do Practical Django Projects e release manager do Django.

Os caras são bons, claro, o Jacob e o Adrian são geniais por terem bolado um framework simples e integrado quando os concorrentes em Python eram todos complicados demais, desintegrados ou as duas coisas ao mesmo tempo. Mas na comunidade Python Brasil eu conheço várias pessoas que manjam mais de Python e de desenvolvimento Web que eles. Falta só um pouco mais de disposição para contribuir internacionalmente. Na verdade, a nossa galera de Plone já é reconhecida mundialmente, mas ainda temos condição de ocupar muito espaço fora do mundo Zope.

Apesar do comentário acima, aprendi bastante na palestra. O James especialmente tem muito o que dizer, se bem que quase tudo que ele falou eu já conhecia porque tem no livro que ele escreveu. Os slides estão online [1] e depois eu vou comentar com mais tempo a apresentação pois fiz umas anotações.

[1] http://jacobian.org/speaking/2009/real-world-django/

Tuesday, March 24, 2009

Metaprogramação 2: o caso do method_missing

Depois de um rigoroso regime de Java ou Delphi, as pessoas ficam maravilhadas ao encontrar na classe Object da linguagem Ruby o método method_missing.

Como fazer isto em Python? Bom, uma busca rápida no Google revela uma solução feita por um programador Ruby que recentemente adotou Python. Ele complicou demais a questão. Vou dar a minha solução mais pragmática.

Antes de mais nada, o que faz o method_missing?

Quando o interpretador Ruby encontra uma invocação de um método que não existe, digamos x.spam, ele faz uma segunda tentativa invocando x.method_missing(:spam) . Se a classe de x não implementa este method_missing, ela herda a implementação da classe Object (veja method_missing na documentação do Ruby), que apenas reporta um erro.

A graça é que você pode implementar method_missing nas suas classes, e fazer o que quiser. Uma aplicação comum é implementar métodos dinâmicos que fazem coisas diferentes dependendo do nome que foi invocado. Em Rails você vê coisas como Cliente.find_by_cpf, onde cpf é um atributo, e find_by_cpf é um método que não existe mas a invocação é tratada por um method_missing que faz uma busca por CPF.

Origem da idéia

Não sei se este mecanismo existia antes do Smalltalk, mas ele fazia parte do Smalltalk-80, lançado há quase 30 anos. Em Smalltalk o método se chamava doesNotUnderstand: e tem um artigo co-escrito pelo Ralph Johnson (da Gangue dos Quatro) que discute o uso dele em metaprogramação.

E em Python?

Python é diferente de Ruby porque a interface pública dos objetos em Python é formada por apenas por atributos, e um método é simplesmente um atributo invocável (callable). Em Ruby a interface pública dos objetos é formada só por métodos. Então o mecanismo análogo em Python lida com atributos não encontrados, e não com métodos. O nome poderia ser attribute_missing mas na verdade é __getattr__. Para entender o __getattr__, tem um exemplo simples no meu texto anterior.

O exemplo de method_missing documentação do Ruby pode ser implementando em Python de forma muito simples usando apenas __getattr__:


# coding: utf-8

'''
Instâncias de Romano têm atributos dinâmicos que devolvem o valor
inteiro correspondente ao numeral romano acessado::

>>> r = Romano()
>>> r.III
3
>>> r.mmix # caixa-baixa também funciona
2009

'''
# Inspirado pela documentação do método Object#method_missing da linguagem Ruby
# e aproveitando o módulo roman.py de Mark Pilgrim, publicado no livro
# Dive into Python: http://diveintopython.org/

from roman import fromRoman, InvalidRomanNumeralError

class Romano(object):
def __getattr__(self, s):
try:
return fromRoman(s.upper())
except InvalidRomanNumeralError:
raise AttributeError('Numeral romano invalido: %r' % s)

if __name__=='__main__':
import doctest
doctest.testmod()



Note que na solução pythônica, acessar r.x significa acessar um atributo cujo valor é calculado dinamicamente, e não acessar um método. Se o objetivo é acessar um método mesmo, basta que o __getattr__ retorne um objeto invocável. No próximo post, a solução para isso.

Monday, March 23, 2009

Metaprogramação em Python

Recentemente eu dei um curso de Python para uns programadores muito bons que curtem Ruby, e eles queriam saber sobre "metaprogramação" em Python.

Python tem uma longa e sólida tradição de metaprogramação, mas esse não é um termo que a gente usa muito na comunidade Python. Meus exemplos de metaprogramação sempre ficaram numa pasta chamada "introspecção". Realmente temos muito que aprender com a comunidade Ruby no quesito marketing. Metaprogramação é muito mais sexy.

Para ilustrar metaprogramação em Python, fiz este pequeno exemplo inspirado numa feature muito importante do Rails 3, que é a possibilidade de acessar itens usando ordinais como first, second etc. Dizem que o DHH implementou todos os ordinais até fortysecond, mas eu não testei.

A idéia fica bem clara num doctest:

'''
Os itens contidos em objetos da classe Lista podem ser acessados pelos
ordinais de 'primeiro' até 'decimo', ou por abreviaturas de três letras
destes ordinais:

>>> l = Lista([11, 22, 33, 44, 55])
>>> l.primeiro
11
>>> l.ter
33

'''
A implementação ficou assim:

from itertools import count

class Lista(list):

__ordinais = ('primeiro segundo terceiro quarto quinto sexto setimo'
' oitavo nono decimo').split()
__abrevs = [s[:3] for s in __ordinais]

def __getattr__(self, atrib):
atr = atrib[:3]
for i, ordinal, abrev in zip(count(), self.__ordinais, self.__abrevs):
if atrib == ordinal or atr == abrev:
return self[i]
else:
msg = "'%s' object has no attribute '%s'"
raise AttributeError(msg % (self.__class__.__name__, atrib))

@property
def ultimo(self):
return self[-1]

ult = ultimo


Vou comentar alguns lances que podem ser novidade para alguém.

Economia de aspas e vírgulas

Quando faço listas de identificadores tipo aquela dos __ordinais eu costumo colocar tudo numa única string e depois usar split, para economizar a digitação de um monte de aspas e vírgulas. Mas isso não é ineficiente? Não, porque aquela atribuição acontece só uma vez, quando o módulo é importado a primeira vez.

O que faz o __getattr__

O método especial __getattr__ é invocado quando um atributo inexistente é acessado. A obrigação dele é devolver um valor, ou levantar AttributeError. Existe também um __setattr__ e até um __delattr__. Tem também um __getattribute__ nas classes novas, mas este é mais difícil de usar corretamente.

Desde que Python ganhou o mecanismo de property, a gente usa menos estes métodos, mas eles ainda são essenciais.

Iterar com índices

O comando for em Python é mais fácil de usar que em outras linguagens, porque não obriga a lidar com o índice que em geral não interessa.

Mas às vezes a gente precisa do índice, e neste caso basta fazer
for i, item in enumerate(sequencia):
No for deste __getattr__ eu usei zip e count porque queria formar tuplas de três elementos: o índice, o ordinal e a abreviatura. Vale muito a pena estudar o que tem lá no módulo itertools: entender como usar aquelas funções vai te fazer pensar diferente.

Propriedade

Em Python uma property serve para implementar um atributo calculado, ou seja, um método que pode ser invocado usando a sintaxe de acesso a atributo (i.é. x.a em vez de x.a()).

Para definir uma propriedade apenas para leitura, usamos o decorador @property acima do método em questão.

E por último, ult = ultimo

Propriedades, funções e até classes em Python são objetos de primeira classe, ou seja, podem ser atribuidos a variáveis, passados como parâmetros e devolvidos como resultados de expressões. Isso significa que para criar uma propriedade ult, bastou atribuir ultimo a este identificador. Simples e elegante.

Este post mal arranhou o vasto e fascimante tema da metaprogramação em Python. Uma ótima referência para o tema é a palestra do Werneck na PyConBrasil 2007 em Joinville: "O que o Python faz quando você não está olhando".