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