Páginas

Sunday, March 29, 2009

Como falar muito sem dizer nada

Essa semana um professor nos enviou um documento bem humorado dizendo como se comportar em reuniões / apresentações.
Trata-se de uma tabela em que você pode formar frases sintaticamente corretas, porém sem nenhum conteúdo, bastando pegar um trecho de cada coluna.

Coluna 1 Coluna 2 Coluna 3 Coluna 4
Caros colegas, a execução deste projeto nos obriga à análise das nossas opções de desenvolvimento futuro.
Por outro lado, a complexidade dos estudos efetuados cumpre um papel essencial na formulação das nossas metas financeiras e administrativas.
Não podemos esquecer que a atual estrutura de organização auxilia a preparação e a estruturação das atitudes e das atribuições da diretoria.
Do mesmo modo, o novo modelo estrutural aqui preconizado contribui para a correta determinação das novas proposições.
A prática mostra que o desenvolvimento de formas distintas de atuação assume importantes posições na definição das opções básicas para o sucesso do programa.
Nunca é demais insistir que a constante divulgação das informações facilita a definição do nosso sistema de formação de quadros.
A experiência mostra que a consolidação das estruturas prejudica a percepção da importância das condições apropriadas para os negócios.
É fundamental ressaltar que a análise dos diversos resultados oferece uma boa oportunidade de verificação dos índices pretendidos.
O incentivo ao avanço tecnológico, assim como o início do programa de formação de atitudes acarreta um processo de reformulação das formas de ação.
Assim mesmo, a expansão de nossa atividade exige precisão e definição dos conceitos de participação geral.

Então resolvi fazer um rápido script Python para formar frases pra mim, algo como "Quote-of-the-day".

# -*- coding: utf-8 -*-
# phrases.py
from random import choice
from textwrap import dedent

try:
from itertools import product
except ImportError:
# http://docs.python.org/library/itertools.html#itertools.product
def product(*args, **kwds):
# product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
# product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
pools = map(tuple, args) * kwds.get('repeat', 1)
result = [[]]
for pool in pools:
result = [x+[y] for x in result for y in pool]
for prod in result:
yield tuple(prod)


_COLUNAS_ = [[u"Caros colegas,",
u"Por outro lado,",
u"Não podemos esquecer que",
u"Do mesmo modo,",
u"A prática mostra que",
u"Nunca é demais insistir que",
u"A experiência mostra que",
u"É fundamental ressaltar que",
u"O incentivo ao avanço tecnológico, assim como",
u"Assim mesmo,"],
[u"a execução deste projeto",
u"a complexidade dos estudos efetuados",
u"a atual estrutura de organização",
u"o novo modelo estrutural aqui preconizado",
u"o desenvolvimento de formas distintas de atuação",
u"a constante divulgação das informações",
u"a consolidação das estruturas",
u"a análise dos diversos resultados",
u"o início do programa de formação de atitudes",
u"a expansão de nossa atividade"],
[u"nos obriga à análise",
u"cumpre um papel essencial na formulação",
u"auxilia a preparação e a estruturação",
u"contribui para a correta determinação",
u"assume importantes posições na definição",
u"facilita a definição",
u"prejudica a percepção da importância",
u"oferece uma boa oportunidade de verificação",
u"acarreta um processo de reformulação",
u"exige precisão e definição"],
[u"das nossas opções de desenvolvimento futuro.",
u"das nossas metas financeiras e administrativas.",
u"das atitudes e das atribuições da diretoria.",
u"das novas proposições.",
u"das opções básicas para o sucesso do programa.",
u"do nosso sistema de formação de quadros.",
u"das condições apropriadas para os negócios.",
u"dos índices pretendidos.",
u"das formas de ação.",
u"dos conceitos de participação geral."]]

frases_vazias = list(product(*_COLUNAS_))
print dedent('''\
De %d frases vazias, escolhi:
"%s"''') % (len(frases_vazias), ' '.join(choice(frases_vazias)))

##for frase in product(*_COLUNAS_):
## print ' '.join(frase)

Sunday, March 22, 2009

Web Frameworks

Essa semana eu compilei uma lista de diversos frameworks para desenvolvimento web.
Não me limitei a Python, (quase) todas as linguagens foram consideradas e ainda tentei "separar o joio do trigo" marcando os frameworks mais 'badalados' em cada linguagem.

Eu fiz o original em um mapa mental no MindManager, mas como o formato é proprietário, fica aqui o PNG com o mapa completo exportado.



A lista é a seguinte:

  1. ActionScript
    • Flex
  2. ASP.NET
    • ASP.NET MVC
    • DotNetNuke
    • MonoRail
    • BFC
    • CSLA
    • Umbraco
  3. ColdFusion
    • ColdSpring
    • Fusebox
    • Model-Glue
    • onTap
  4. Groovy
    • Grails
  5. Java
    • Apache Struts 2
    • JavaServer Faces
    • JBoss Seam
    • Spring
    • Stripes
    • Tapestry
    • Apache
      • Click
      • Cocoon
      • Wicket
    • AppFuse
    • Aranea
    • Google Web Toolkit
    • Hamlets
    • ItsNat
    • IT Mill Toolkit
    • OpenLaszlo
    • OpenXava
    • Reasonable Server Faces
    • RIFE
    • Shale
    • SmartClient
    • Sofia
    • ThinWire
    • WebObjects
    • WebWork
    • ztemplates
  6. JavaScript
    • Ajile
    • Axiom Stack
    • WaveMaker
  7. Lua
    • Orbit
    • Kepler
  8. Perl
    • Catalyst
    • Interchange
    • Mason
    • Maypole
  9. PHP
    • CakePHP
    • Prado
    • Symfony
    • Zend
    • CodeIgniter
    • Drupal
    • Akelos
    • eZ Components
    • FUSE
    • Horde
    • Kohana
    • LISA
    • Midgard
    • Nette
    • PEAR
    • Orinoco
    • Qcodo
    • Simplicity
    • SilverStripe (Sapphire)
    • SPIP
    • Tigermouse
    • Zoop
  10. Python
    • Django
    • TurboGears
    • Pylons
    • Zope
    • Grok
    • web2py
    • CherryPy
    • CubicWeb
    • Enamel
    • Gizmo(QP)
    • Glashammer
    • Karrigell
    • notmm
    • Porcupine
    • Pyroxide
    • QP
    • SkunkWeb
    • Spyce
    • web.py
    • Webware
    • Werkzeug
  11. Ruby
    • Ruby on Rails
    • Merb
    • Camping
    • Nitro
  12. Smalltalk
    • Seaside
    • AIDA/Web

Saturday, March 21, 2009

Python tricks: locals(), globals() e keyword arguments (kwargs)

Nessa semana durante uma sessão de pair programming lá na globo.com chegamos a um código que começava a se repetir... um bom momento para melhora, e lá introduzi uma "técnica" muito legal: usar o um dicionário que contém o escopo local para dinamizar o acesso a variáveis/nomes.

Esse dicionário já existe builtin no Python, apesar de eu acreditar que muita gente não saiba ou não o use...
Trata-se do locals() (e seu irmão globals()).

Uso:
def preencher_mensagem(id_mensagem, titulo=None, subtitulo=None, link=None):
id_titulo = MENSAGENS[id_mensagem]['titulo']
id_subtitulo = MENSAGENS[id_mensagem]['subtitulo']
id_link = MENSAGENS[id_mensagem]['link']

if titulo is not None:
preencher_titulo(id_titulo, titulo)

if subtitulo is not None:
preencher_subtitulo(id_subtitulo, subtitulo)

if link is not None:
preencher_link(id_link, link)


Abstraim o resto do código, e pensem nos if's.
Com mais e mais parametros para preencher, isso fica muito repetitivo.
Por que não um loop?
def preencher_mensagem(id_mensagem, titulo=None, subtitulo=None, link=None):
id_titulo = MENSAGENS[id_mensagem]['titulo']
id_subtitulo = MENSAGENS[id_mensagem]['subtitulo']
id_link = MENSAGENS[id_mensagem]['link']

for parte in 'titulo subtitulo link'.split():
texto = locals()[parte]
id = locals()['id_%s' % parte]
preencher = globals()['preencher_%s' % parte]

if texto is not None:
preencher(id, texto)

Notem que com o locals() podemos acessar o dicionário de nomes locais e usar seus valores tanto para leitura quanto para escrita (não recomendada), chamar métodos, etc. O equivalente para o escopo global é o globals().
Ainda poderíamos fazer melhor e usar um dicionário dos parâmetros passados para a função/método, ao invés de 'apelar' para o escopo local.
Basta colocar um argumento que leve ** na frente, e ele será um dicionário de todos os parâmetros passados por nome. Melhor definição, formalismo e mais exemplos na documentação oficial do Python.
def preencher_mensagem(id_mensagem, **partes):
for parte, texto in partes.iteritems():
id = MENSAGENS[id_mensagem][parte]
preencher = globals().get('preencher_%s' % parte)

if preencher is not None:
preencher(id, texto)

Agora, para os que querem rodar alguma coisa que funcione, fiz um script completo que pode ser executado.
Fiz um "banco de dados" fictício só para fins de demonstração.

# -*- coding: utf-8 -*-
# Exemplo usado no meu blog em lifeatmymind.blogspot.com
# Rodolfo Carvalho 2009-03-21

#----- Meu "Banco de Dados" ------------------
MENSAGENS = {1: {'titulo': 4,
'subtitulo': 3,
'link': 1},
2: {'titulo': 1,
'subtitulo': 4,
'link': 2},
3: {'titulo': 2,
'subtitulo': 2,
'link': 4},
4: {'titulo': 3,
'subtitulo': 1,
'link': 3}}

TITULOS = {1: '', 2: '', 3: '', 4: ''}
SUBTITULOS = {1: '', 2: '', 3: '', 4: ''}
LINKS = {1: '', 2: '', 3: '', 4: ''}
#---------------------------------------------

def preencher_titulo(id_titulo, titulo):
TITULOS[id_titulo] = titulo

def preencher_subtitulo(id_subtitulo, subtitulo):
SUBTITULOS[id_subtitulo] = subtitulo

def preencher_link(id_link, link):
LINKS[id_link] = link

#---------------------------------------------

def preencher_mensagem1(id_mensagem, titulo=None, subtitulo=None, link=None):
id_titulo = MENSAGENS[id_mensagem]['titulo']
id_subtitulo = MENSAGENS[id_mensagem]['subtitulo']
id_link = MENSAGENS[id_mensagem]['link']

if titulo is not None:
preencher_titulo(id_titulo, titulo)

if subtitulo is not None:
preencher_subtitulo(id_subtitulo, subtitulo)

if link is not None:
preencher_link(id_link, link)

def preencher_mensagem2(id_mensagem, titulo=None, subtitulo=None, link=None):
id_titulo = MENSAGENS[id_mensagem]['titulo']
id_subtitulo = MENSAGENS[id_mensagem]['subtitulo']
id_link = MENSAGENS[id_mensagem]['link']

for parte in 'titulo subtitulo link'.split():
texto = locals()[parte]
id = locals()['id_%s' % parte]
preencher = globals()['preencher_%s' % parte]

if texto is not None:
preencher(id, texto)

def preencher_mensagem3(id_mensagem, **partes):
for parte, texto in partes.iteritems():
id = MENSAGENS[id_mensagem][parte]
preencher = globals().get('preencher_%s' % parte)

if preencher is not None:
preencher(id, texto)

#---------------------------------------------

def imprimir_mensagens():
def print_linha(conteudo=''):
print '| %s |' % conteudo.center(76)

for id, msg in MENSAGENS.iteritems():
print
print '(%d)' % (id,)
print '-' * 80
print_linha('* %s *' % TITULOS[msg['titulo']])
print_linha('%s' % SUBTITULOS[msg['subtitulo']])
print_linha()
print_linha('%s' % LINKS[msg['link']])
print '-' * 80

if __name__ == '__main__':
preencher_mensagem = preencher_mensagem3

preencher_mensagem(id_mensagem=1,
titulo=u'Olá mundo!',
subtitulo=u'Veja como é divertido usar Python',
link='http://lifeatmymind.blogspot.com')
preencher_mensagem(id_mensagem=2,
titulo=u'Esta é a segunda mensagem cadastrada',
link='http://lifeatmymind.blogspot.com')
preencher_mensagem(id_mensagem=3,
subtitulo=u'Eu não tenho título...',
link='http://lifeatmymind.blogspot.com')
preencher_mensagem(id_mensagem=4,
titulo=u'Última mensagem',
subtitulo='Sou uma mensagem sem link')
imprimir_mensagens()



Um outro exemplo de uso interessante seria:

def tell_story(king, princess, action):
print ('There was a King called %(king)s that had a beautiful daughter. '
'Her name, %(princess)s, would have to be shout in order to make '
'her %(action)s.' % locals())

tell_story("Peter", "Fiona", "ride a horse")
tell_story("Allan", "Britney", "dance")


No trecho acima tem duas coisas interessantes:
  1. Você pode escrever strings grandes sem poluir seu código com linhas super extensas. Siga a recomendação de manter no máximo 80 caracteres por linha. Para escrever strings longas, use-se do artifício da continuação de linha implícita por causa dos parênteses e da concatenação automática de strings postas lado a lado.
>>> "Eu sou uma string" " que continua em outra parte"
'Eu sou uma string que continua em outra parte'

>>> ("Eu sou uma string" " que continua em outra parte"
... " e tambem em outra linha!")
'Eu sou uma string que continua em outra parte e tambem em outra linha!'

  1. Você pode usar o locals() como dicionário para formatação de strings!

O código original é equivalente a:
def tell_story(king, princess, action):
print ('There was a King called %(king)s that had a beautiful daughter. '
'Her name, %(princess)s, would have to be shout in order to make '
'her %(action)s.' % dict(king=king, princess=princess, action=action))

tell_story("Peter", "Fiona", "ride a horse")
tell_story("Allan", "Britney", "dance")

Porém, o original é bem mais sucinto :)

Wednesday, March 18, 2009

Provérbio do dia

"O maior dos erros é a pressa antes do tempo, e a lentidão ante a oportunidade"
- Provérbio árabe


Do (excelente) livro "Do Fortran à Internet: no rastro da trilogia Educação, Pesquisa e Desenvolvimento", de Tércio Pacitti.

Saturday, March 14, 2009

Escreva menos, use Python

Ontem vivenciei uma situação em que tivemos que manter um código legado para inserir mais uma pequena funcionalidade.
Uma "regra" aqui é não se desviar do que você tem que fazer (que possui mais valor de negócio) para cuidar de outras coisas. Outra "regra" é não deixar débito técnico.
A grande dificuldade é encontrar o balanço adequado entre esses dois objetivos, e ainda assim implementar sua nova funcionalidade.

Bem, livre de qualquer "regra", vou aproveitar esse post para introduzir 3 recursos do Python que nem sempre são lembrados, e que nos permitem economizar linhas de código, escrevendo de forma clara e concisa o que queremos fazer.

  1. Método get dos dicionários;
  2. Método split das strings;
  3. Função builtin/global setattr.
Vamos partir do seguinte código legado:

#...
from django.shortcuts import render_to_response
from model import Cadastro
#...

def tratar_cadastro(request):
post = request.POST
#...
cadastro = Cadastro()
#...

if post.has_key('nome'):
cadastro.nome = post['nome']

if post.has_key('endereco'):
cadastro.endereco = post['endereco']

if post.has_key('telefone'):
cadastro.telefone = post['telefone']

if post.has_key('email'):
cadastro.email = post['email']

return render_to_response('cadastro/index.html', {"cadastro": cadastro})


É um esboço de uma View do Django. Como podem ver, espera-se que a view seja requisitada via HTTP POST, com a passagem de diversos parâmetros, que ficam armazenados no dicionário Python request.POST. Esse papo de cadastro, nome, telefone, etc é só para ilustrar.
Então o código que temos é uma sequência de if's que setam atributos de uma instância de Cadastro que é posteriormente passada para o template.

Podemos imaginar que temos um formulário numa página que, ao ser submetido, exibe as informações preenchidas para confirmação.

Agora temos que adicionar mais um campo ao cadastro. Digamos que nosso cliente pediu para guardar também o campo "idade". Por simetria, acabaria acontecendo o seguinte:
#...
from django.shortcuts import render_to_response
from model import Cadastro
#...

def tratar_cadastro(request):
post = request.POST
#...
cadastro = Cadastro()
#...

if post.has_key('nome'):
cadastro.nome = post['nome']

if post.has_key('idade'):
cadastro.idade = post['idade']


if post.has_key('endereco'):
cadastro.endereco = post['endereco']

if post.has_key('telefone'):
cadastro.telefone = post['telefone']

if post.has_key('email'):
cadastro.email = post['email']

return render_to_response('cadastro/index.html', {"cadastro": cadastro})


Pra que mudar o que já funciona? Bem, vamos então ao primeiro dos tópicos.
Usar o dict.has_key é ruim e desnecessário. Esse método é deprecated no Python 2.6 e já foi removido da linguagem no Python 3.0, em favor da forma chave in dicionario. No lugar dele e do if deveríamos usar o dict.get.
Mas o que esse método faz? Ele retorna o valor de dict['key'] se o dicionário tem a chave 'key', senão retorna None ou outro valor padrão que o programador especificar.
Ficaria assim:
#...
from django.shortcuts import render_to_response
from model import Cadastro
#...

def tratar_cadastro(request):
post = request.POST
#...
cadastro = Cadastro()
#...

if post.has_key('nome'):
cadastro.nome = post['nome']

cadastro.idade = post.get('idade', u'Campo não preenchido')

if post.has_key('endereco'):
cadastro.endereco = post['endereco']

if post.has_key('telefone'):
cadastro.telefone = post['telefone']

if post.has_key('email'):
cadastro.email = post['email']

return render_to_response('cadastro/index.html', {"cadastro": cadastro})


Além de economizar linhas, temos um código menos redundante e seguindo o princípio de "It's better to beg forgiveness than to ask permission".
Vamos refatorar o resto do código:
#...
from django.shortcuts import render_to_response
from model import Cadastro
#...

def tratar_cadastro(request):
post = request.POST
#...
cadastro = Cadastro()
#...

cadastro.nome = post.get('nome')
cadastro.idade = post.get('idade')
cadastro.endereco = post.get('endereco')
cadastro.telefone = post.get('telefone')
cadastro.email = post.get('email')

return render_to_response('cadastro/index.html', {"cadastro": cadastro})


Assim, caso um dos campos não venha no POST, será atribuído o valor None ao atributo do objeto cadastro.
Notem que, assim como antes, existe bastante duplicação de código (por razões práticas, fiz um exemplo com poucas chaves do dicionário, mas o caso que me deparei eram mais que dez...).
Agora então é a hora de introduzir os outros dois conceitos, o split e o setattr.
O primeiro transforma uma string em lista através de um separador. Exemplo:
>>>  print "um, dois, tres".split(", ")
['um', 'dois', 'tres']

Isso pode ser usado para declararmos uma lista sem precisar de muitas aspas e vírgulas... basta colocar todos os itens da lista em uma string e usar o split.
O outro faz o mesmo que objeto.atributo = valor, porém atributo pode ser algo dinâmico, definido em tempo de execução.
Com essas mudanças, nosso código ganha uma tremenda compressão, sem perder legibilidade:
#...
from django.shortcuts import render_to_response
from model import Cadastro
#...

def tratar_cadastro(request):
post = request.POST
#...
cadastro = Cadastro()
#...

for attr in 'nome idade endereco telefone email'.split():
setattr(cadastro, attr, post.get(attr))


return render_to_response('cadastro/index.html', {"cadastro": cadastro})