quinta-feira, outubro 30, 2008

Decorators com argumentos

Nos últimos tempos estive escrevendo uma API que utiliza decorators para registrar eventos em uma aplicação PyGTK/Glade. Nessa API basicamente eu tenho eventos registrados no glade e associados a um handler na aplicação (glade.sinal_connect('event', handler)) e eventos conectados diretamente na aplicação associados a um handler e um widget (widget.connect('event', handler)).

Então precisei de um decorator que em alguns momentos não receberia nenhum parâmetro, associando a função decorada a um sinal registrado no glade com o mesmo nome da função.

# Associa a função 'destroy' ao evento 'destroy' já registrado no glade

@handler
def destroy(self, *args):
print args

# Mesmo funcionamento

@handler()
def destroy(self, *args):
print args


Ou ainda, este mesmo decorator poderia receber argumentos detalhando qual o nome do evento e widget para quais a função decorada deve ser associada.

# Associa a função 'x' ao evento 'destroy' já registrado no glade

@handler('destroy')
def x(self, *args):
print args

# Associa a função y ao evento 'on_bt_ok_clicked' no widget bt_ok

@handler('on_bt_ok_clicked', bt_ok)
def y(self, *args):
print args


Quando nenhum argumento é informado (@decorator()) utilizo uma função aninhada para ser retornada como decorator e assim efetivamente decorar a função. O mesmo funcionamento se aplica quando os argumentos não são funções ou métodos (@decorator('x'), @decorator('x', a=1)), utilizando o módulo inspect . Esse comportamento só muda quando o decorator não recebe parâmetros, ou seja, quando este não é invocado, retornando a função ou método recebido sem nenhuma modificação.

def decorator(*args, **kargs):
import inspect
# @decorator() or @decorator('x') or @decorator('x', d='a')
if not args or not inspect.isroutine(args[0]):
def deco(func):
def wrapper(*f_args, **f_kargs):
return func(*f_args, **f_kargs)
return wrapper
return deco
elif inspect.isroutine(args[0]): # @decorator
return args[0]


Exemplos de uso:

@decorator
def d(x, y, d=1):
print x, y, d
#d = decorator(d)

@decorator()
def e(x, y, d=1):
print x, y, d
#e = decorator()(e)

@decorator('x')
def f(x, y, d=1):
print x, y, d
#f = decorator('x')(f)

@decorator('x', d=5)
def g(x, y, d=1):
print x, y, d
#f = decorator('x', d=5)(g)


Abaixo o mesmo decorator utilizando classe, assim o comportamento do decorator pode ser facilmente expandido para funcionar com métodos seguindo esse texto Decorators and Descriptors, fazendo o decorator ser ainda mais flexível podendo ser utilizado com funções ou métodos ao mesmo tempo:

class decorator(object):
def __init__(self, *args, **kargs):
import inspect
# @decorator() or @decorator('x') or @decorator('x', d='a')
if not args or not inspect.isroutine(args[0]):
def deco(func):
def wrapper(*f_args, **f_kargs):
return func(*f_args, **f_kargs)
return wrapper
self.func = deco
elif inspect.isroutine(args[0]): # @decorator
self.func = args[0]

def __call__(self, *args, **kargs):
return self.func(*args, **kargs)

def __get__(self, obj, type=None):
if obj is None:
return self
new_func = self.func.__get__(obj, type)
return self.__class__(new_func)


Exemplos de uso:

class A:
@decorator
def b(self, x, y):
print x, y

a = A()
a.b(1, 2)

@decorator
def b(x, y):
print x, y

b(2, 3)

sexta-feira, setembro 26, 2008

Python e MVC - Parte 2

Continuando o post anterior Python e MVC - Parte 1.

Durante o desenvolvimento de uma aplicação aqui na empresa estive decidido a utilizar um framework MVC para Python que além de me prover as ferramentas necessárias para resolver os meus dilemas de design como manutenção facilitada, separação da aplicação em partes lógicas e criação de uma estrutura de componentes este também deveria ser genérico o suficiente para permitir a troca da view na minha aplicação de maneira simples e fácil.

O problema é que hoje existem basicamente três frameworks MVC para Python: Kiwi, pygtkmvc e PureMVC. O Kiwi é um framework muito bom e desenvolvido para uma aplicação real o Stoq, o pygtkmvc apesar de ter idéias interessantes parece ser um projeto abandonado e o PureMVC por sua vez é um projeto iniciado em ActionScript que mais tarde foi portado para diversas linguagens entre elas Python.

O Kiwi e o pygtkmvc são altamente acoplados com a view no caso PyGtk, o que vai totalmente contra o princípio de fraco acoplamento entre as partes como diz o MVC, e isso IMHO é muito importante e também é um requisito da minha aplicação. Sendo assim, sobra apenas nosso amigo PureMVC.

Eu também poderia construir meu próprio framework MVC, de acordo com minhas necessidades e me baseando nestes últimos três exemplos, reunindo assim o melhor de cada um. Sem dúvida, essa seria a melhor alternativa, mas como sempre o tempo era curto e essa não era uma possibilidade. Sem falar no esforço enorme de análise e um conhecimento muito profundo de design patterns necessário para se criar algo genérico o suficiente.

O PureMVC possui alguns problemas, IMHO. O primeiro problema é seu estilo ou filosofia e sua nomenclatura que são extremamente diferentes do que estamos acostumados em Python. A implementação então nem se fala, obviamente foi feita por um programador Java ou algo do tipo, embora funcione.

Algumas coisas não seguem uma linha de simplicidade e facilidade no uso, tornando coisas simples um pouco complexas demais. Como exemplo, o código abaixo foi retirado do demo do projeto que utiliza wxPython:


class ProductListMediator(Mediator, IMediator):
NAME = "ProductListMediator"
product_proxy = None
def __init__(self, view_component):
self.view_component = view_component
super(ProductListMediator, self).__init__(self.NAME, view_component)


O código acima pode ser simplicado com uma meta class como essa:


class MetaMediator(type):
def __init__(cls, name, bases, namespace):
cls.NAME = name


class BaseMediator(Mediator, IMediator):
__metaclass__ = MetaMediator
def __init__(self, view_component=None, constant_list=[]):
self.constant_list = constant_list
super(BaseMediator, self).__init__(self.NAME, view_component)



Obtendo o mesmo resultado que o código original sem perder em flexibilidade:


class ProductListMediator(BaseMediator):
product_proxy = None


O PureMVC reune uma gama de padrões em uma solução bem interessante. Todas as partes do MVC são agrupadas e invocadas por um padrão facade, este facade invoca o controller através do padrão command utilizando um esquema de execução por variáveis globais, com essa solução o controller não fica acoplado ao facade, já que se o nome do método for alterado, basta alterar em um local, no alias definido na variável global.


class AppFacade(Facade):
ON_ACT_STARTUP = 'on_act_startup'

@staticmethod
def getInstance():
return AppFacade()

def initializeController(self):
super(AppFacade, self).initializeController()
super(AppFacade, self).registerCommand(AppFacade.ON_ACT_STARTUP,\
controller.StartupCommand)


O controller por sua vez utiliza o padrão observer para se comunicar com a view garantido fraca acoplamento com o uso de notificações.


class DeleteCommand(SimpleCommand, ICommand):
def execute(self, note):
product = note.getBody()
product_proxy = self.facade.retrieveProxy(model.ProductProxy.NAME)
product_proxy.delete(product)
self.facade.sendNotification(main.AppFacade.ON_PRODUCT_DELETE)


A view então utilizando o padrão mediator para se comunicar com a visão concreta Gtk, Html, etc mantem mais uma vez acoplamento fraco, se for preciso mudar de Gtk para Html ou Qt, muito pouco do código precisará ser modificado, sanando a minha necessidade inicial.


class DialogMediator(Mediator, IMediator):
NAME = "DialogMediator"
def __init__(self, view_component):
self.view_component = view_component

def listNoticationInterests(self):
return [main.AppFacade.ON_SHOW_DIALOG,]


E por fim o model utiliza o padrão proxy para garantir abstração por interface, assim proporcionando independência da tecnologia utilizada no model, ORM, XML, serialização, etc.


class ProductProxy(Proxy):
NAME = "ProductProxy"
def __init__(self):
super(ProductProxy, self).__init__(self.NAME, [])

def get_all(self):
return value_object.ProductVO.query.all()


O esquema utilizado pelo PureMVC é um tanto complexo a primeira vista, mas se for utilizado com bom senso e sempre com simplicidade em mente o retorno pode ser muito positivo. Além da fraco acoplamento a manutenção fica muito fácil, já que cada entidade da aplicação fica bem separada evitando propagação de erros pela aplicação. Vale lembrar que o PureMVC funciona como um esqueleto para sua aplicação não obrigando o uso explicito de todos os componentes.

Como o PureMVC é feito para um conjunto de linguagens ao qual o Python não se encaixa, uma camada acima do PureMVC se faz necessária afim de simplificar a utilização deste e excluindo as partes desnecessárias, como os exemplos mostrados acima.

Uma dica é simplificar o esquema de notificações do mediator para que as notificações executem os métodos da classe implementa o mediator que tiverem o mesmo nome das notificações, excluindo assim a necessidade do registro e criação das notificações na classe. Como exemplo abaixo, retirado da mesma demo:


def listNotificationInterests(self):
return [
main.AppFacade.ON_PRODUCT_CHANGE,
main.AppFacade.ON_PRODUCT_DELETE,
]

def handleNotification(self, note):
if note.getName() in [main.AppFacade.ON_PRODUCT_CHANGE, main.AppFacade.ON_PRODUCT_DELETE]:
self.view_component.clear()
self.view_component.fill(self.product_proxy.get_all())


Neste parte de código o programador precisa especificar quais as notificações a classe recebe e escrever um método com um monte de condicionais para tratar cada notificação. É muito mas fácil escrever uma metaclass para transformar isso em um esquema de sinal que invoca o sinal que tiver um método na classe com o mesmo nome do sinal.

Fica na minha lista de coisas a fazer uma versão deste framework para Python, com um código e uma filosofia adaptada ao estilo Python de ser.

Por fim um link interessante sobre o PureMVC.

quinta-feira, setembro 18, 2008

Papo de programador

Eu: The class of a class is ...

Programador X: Humm.... another class?

Eu: Não.

Depois de muitas respostas "interessantes"..

Programador Z: Pesquisei no google, é uma metaclass, mas o que é uma metaclass?

Programador Y: Simplificando é uma classe que serve para construir outra classe.

Programador W: Eu uso VI para construir uma classe, então o VI é uma metaclass.

Python e MVC - Parte 1

É fato que durante o desenvolvimento de uma aplicação mais complexa alguns problemas ou dilemas
podem fazer nosso cérebro fritar. Para que o programador alcance uma boa solução é recomendável
que ele se concentre no contexto do problema e da solução. Python alcança esse objetivo de maneira magnífica, com um alto nível de abstração, simplicidade e coesão a linguagem permite que o programador se desvincule da linguagem e fique livre para se focar na solução.

No entanto, alguns problemas não fazem parte do contexto do problema e da solução, estes geralmente são ocasionados por nós mesmos, seja por falta de conhecimento da linguagem em questão, de organização, planejamento ou ainda pelo uso de uma solução pouco elegante ou inadequada.

Um sistema que se inicia de maneira desorganizada e sem planejamento com o passar do tempo e com evolução natural do mesmo, tende a enfrentar dificuldades assustadoras em relação a manutenção.

Qual a solução? Simples, vamos nos organizar, planejando e definindo muito bem como nosso sistema deve ser feito, vamos utilizar um bom processo de analise e pronto! Esse já é um grande passo, mas além desses pontos precisamos nos perguntar, será que os problema que estou tentando resolver já não possuem uma solução?

Sim, alguns problemas já possuem soluções "amplamente" divulgadas e aceitas, essas soluções são chamadas de Design patterns (Padrões de projeto) ou Architectural patterns (Padrões de arquitetura de software).

O MVC (model, view, controller) é um padrão de arquitetura que serve para separar conceitos lógicos da sua aplicação. O grande problema é que o próprio MVC, como todo padrão, por si só, não resolve todos os problemas e algumas vezes pode acabar criando outros problemas. O uso ideal de MVC com alguns cuidados e uma série de padrões de projeto pode nos garantir uma aplicação muito bem escrita e de fácil manutenção.

Inicialmente utilizar MVC e outros padrões fielmente pode ser um tanto chato e cansativo, mas como o tempo esse esforço pode render bons frutos. Aliar esses padrões com a simplicidade e elegância do Python não é um equilíbrio tão fácil de se alcançar, mas que pode ser muito gratificante.

Quando utilizo algum padrão em Python sempre procuro ter em mente que este padrão pode não se aplicar em Python, ou seja, tento não aceitar esses padrões como únicas e infalíveis soluções.

No próximo post vou falar sobre os frameworks MVC para Python (Kiwi, PureMVC, etc), quais as opções e detalhes de implementação.

quinta-feira, setembro 04, 2008

Django 1.0

Depois de uma espera de três anos finalmente foi lançada a versão 1.0 do excelente framework web Django. O lançamento dessa versão representa muito para esse framework que se tornou o mais popular framework web escrito em Python.

A versão 1.0 traz consigo um número muito grande de vantagens para os desenvolvedores, mas a principal é a estabilidade. Com toda certeza essa versão deve definitivamente posicionar esse excelente trabalho da comunidade como uma das melhores escolhas para desenvolvimento web.

Confira todas as novidades!
Experimente essa excelente ferramenta!

Parabéns a toda comunidade.

quinta-feira, agosto 14, 2008

Implementações da Sequência de Fibonacci em Python

O número de Fibonacci [1] ou sequência de Fibonacci é um exemplo clássico utilizado na computação para demonstração de recursividade.

No entanto, existem diversas implementações e geralmente com uma performance melhor que a versão recursiva:

Versão recursiva:

def fibonacci(n):
if n < 2:
return n
else:
return fibonacci(n-1)+fibonacci(n-2)

for x in range(10):
fibonacci(x)


Versão iterativa:

def fibonacci(n):
a,b = 0,1
for i in range(n):
a,b = b,a+b
return a

for x in range(10):
fibonacci(x)


Versão iterativa com "generators" [3]:

def fibonacci():
a, b = 0, 1
while True:
yield a
a,b = b,a+b

f = fibonacci3()
for x in range(10):
f.next()


Versão com "cálculo da proporção áurea (phi)" [4]

from math import log
phi = (1 + 5**0.5) / 2

def fibonacci(n):
return int(round((phi**n - (1-phi)**n) / 5**0.5))

for x in range(10):
fibonacci(x)



Versão recursiva/funcional:

fibonacci = lambda n,a=1,b=1:[b,0][n>0] or fibonacci(n-1,b,a+b)

for x in range(10):
fibonacci(x)


Utilizando o módulo "timeit" [5] repetindo 3 vezes e executando 1000 vezes cada função temos a lista de performance:

Versão recursiva: 0.635288953781
Versão iterativa: 0.0393660068512
Versão iterativa com "generators": 0.0163049697876
Versão com "cálculo da proporção áurea (phi)": 0.0362870693207
Versão recursiva/funcional: 0.100222110748

De longe o pior resultado é a versão recursiva como esperado, e o melhor resultado é da versão iterativa com "generators".

[1] - http://en.wikipedia.org/wiki/Fibonacci_number
[2] - http://pt.wikipedia.org/wiki/Recursividade
[3] - http://www.python.org/dev/peps/pep-0255/
[4] - http://en.wikipedia.org/wiki/Golden_ratio
[5] - http://docs.python.org/lib/module-timeit.html

sexta-feira, março 07, 2008

Python + SSL + Certificado digital

É comum durante o desenvolvimento de um programa que este precise de comunicação com serviços de terceiros disponíveis na rede.

Em Python como em outras linguagens, todo meio de comunicação via rede utiliza basicamente socket em seu nível mais baixo para prover essa comunicação, seja o meio de comunicação SOAP, rpc, http, etc.

Algumas vezes estes serviços externos fornecem a possibilidade de utilização de canais seguros para comunicação (ssl). Em alguns casos o serviço a ser utilizado pode até mesmo obrigar o uso de comunicação segura, para isso precisamos utilizar socket + ssl. Dependendo da necessidade do projeto pode ser necessário utilizar também um certificado digital juntamente com a conexão ssl como forma de autenticação.

Para utilizar comunicação segura o módulo 'socket' prove a função 'ssl', que de maneira simples executa a comunicação com o serviço desejado via ssl. Essa função também pode receber dois parâmetros , 'key_file' e 'cert_file' respectivamente o 'path' do arquivo PEM para a chave privada e o 'path' para o arquivo PEM do certificado.

Uma assinatura digital geralmente é um arquivo binário no formato 'pkcs12' com a extensão '.pfx', sendo assim precisamos abrir esse arquivo e retirar as duas informações que o 'ssl' precisa (key_file, cer_file) e salvar nos arquivos com o formato PEM. Para isso utilizamos openssl e pyopenssl, 'bindings' para python da 'lib' 'openssl'.

Com os módulos instalados, vamos abrir o certificado no formato binário, transformar no objeto 'pkcs12' e então retirar as informações do certificado e da chave privada para em seguida salvar cada um em arquivo separado.

Alguns certificados podem estar codificados sendo necessário informar a frase para decodificação, para tal temos o parâmetro 'passphrase', se não for preciso é só não informar o parâmetro como no exemplo abaixo.


from OpenSSL import crypto

# Lendo o arquivo pfx no formato pkcs12 como binario
pkcs12 = crypto.load_pkcs12(open('certificado.pfx', 'rb').read(), 'passphrase')

# Retorna a string decodificado do certificado
cert_str = crypto.dump_certificate(crypto.FILETYPE_PEM, pkcs12.get_certificate())

# Retorna a string decodificado da chave privada
key_str = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkcs12.get_privatekey())

# Gravando a string no dicso
open('cert.pem', 'wb').write(cert_str)

# Gravando a string no dicso
open('key.ptm', 'wb').write(key_str)


Agora para finalizar basta efetuar a comunicação com o serviço desejado utilizando o modulo 'socket' e a função 'ssl' passando os parâmetros de chave e certificado:


proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
proxy.connect(('www.lzt.com.br', 80))
proxy_ssl = socket.ssl(proxy, 'key.pem', 'cert.pem')


Pronto! Com o objeto 'proxy' podemos fazer requisições a serviços que utilizem conexão 'ssl' com certificados digitais.

terça-feira, fevereiro 27, 2007

Links para o ctypes

Estou dando uma olha no ctypes, vou utilzar aqui na empresa então estou reunindo alguns links sobre o assunto.

Pretendo escrever um tutorial sobre o ctypes, assim que estiver legal posto aqui.

quarta-feira, fevereiro 21, 2007

PyPy - nova versão do projeto

Acaba de sair do forno a versão 0.99.0 do projeto PyPy. IMVHO um dos mais promissores e revolucionários projetos relacionados a Python da atualidade.

Bom, juntei alguns links sobre PyPy. Estou muito impressionado com o projeto e comecei a estudar o modelo de funcionamento dele, assim que tiver algo concreto postarei aqui.

terça-feira, fevereiro 06, 2007

Ordenando listas no python2.3 e inferiores

Ordenação simples

Um objeto to tipo 'list' possui um método chamado 'sort' que faz a ordenação 'in-place' (resultado na própria variável) do objeto.

Descrição do método:

L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1, 0, 1'

Exemplo:

>>> lista = ['a', 'c', 'b']
>>> lista.sort()
>>> lista
['a', 'b', 'c']
>>>


Ordenação simples inversa


Para ordenar uma lista do maior para o menor item temos o método 'reverse', que trabalha de maneira análoga ao método 'sort'.

Exemplo:

>>> lista.reverse()
>>> lista
['c', 'b', 'a']
>>>

Ordenação de lista de dicionários por uma chave

O metodo 'sort' aceita uma função de comparação como paramêtro. Essa função caso seja informada será utilizada para comparar os itens da lista e resolver qual item prioridade na ordem sobre os demais.

Para que o método ordene a lista se baseando na chave dos dicionários a função criada sobrescreve a função 'bulti-in' 'cmp' do python.

A função 'cmp' como podemos ver abaixo, compara dois objetos recebidos como paramêtro retornando um inteiro que informa qual dos itens é maior ou se os dois são iguais. Baseado no resultado da função 'cmp' a função 'sort' ira ordenar a lista.

Descrição do método:

'cmp(x, y) -> integer\n\nReturn negative if xy.'

Em nosso exemplo desejamos ordenar a lista utilizando como base a chave (coluna) '2' do dicionário e assim a definimos.

Quando a função 'sort' estiver interando sobre a lista, ela vai passar cada item encontrado para a nossa função sort modificada 'mycmp' que por sua vez irá chamar a funcao 'cmp' passando como paramêtro o valor das chaves no dicionário pelo qual desejamos ordenar.

>>> lista = [{1: 'b', '2': 'b'}, {1: 'c', '2': 'a'}]
>>>
... def mycmp(a, b):
... print a
... print b
... return cmp(a.get('2'), b.get('2'))
...
>>> lista.sort(mycmp)
>>> lista
[{1: 'b', '2': 'b'}, {1: 'c', '2': 'a'}]

Também podemos utilizar 'lambda' para criar uma função anônima. O resultado é o mesmo, talvez com alguma variação de performance.

lista.sort(lambda x,y: cmp(a.get('2'), b('2')))

Ordenar listas de dicionários de acordo com uma ou mais chaves escolhidas

A função abaixo recebe dois paramêtros, a lista que se deseja ordenar e a lista de chaves que devem ditar a ordenação.

>>> lista = [{1: 1, '2': 'b', '3': 0}, {1: 1, '2': 'a', '3': -1}, {1: 0, '2': 'd', '3': 5}]
>>> def sort_list(l, sort_keys):
... def mycmp(a, b):
... k1 = map(lambda x: a.get(x), sort_keys)
... k2 = map(lambda x: b.get(x), sort_keys)
... return cmp(k1, k2)
...
... l.sort(mycmp)
... return l
...
>>> sort_list('1', lista, ['2', '1'])
[{1: 1, '3': -1, '2': 'a'}, {1: 1, '3': 0, '2': 'b'}, {1: 0, '3': 5, '2': 'd'}]

Criamos uma função 'closure' chamada 'mycmp', que como no exemplo anterior será passada para a função 'sort'. Dentro da funcao 'mycmp' é onde temos a diferença básica em relacao a função anterior. Ao invés de dizer qual o resultado de cada dicionário que ele deve utilizar para ordenar, passamos duas listas com os valores das colunas pelas quais desejamos ordenar.

Novamente, o 'sort' utlizando a funcao 'cmp' irá decidir qual dos dois objetos deve ter a preferência e assim ordenar a lista de dicionários de acordo com as colunas que especificamos.

Atenção, alguns módulos como por exemplo 'mxDatetime' sobrescrevem o método sort para que funcionem a seu gosto. Isso pode fazer com o que o método 'cmp' que estiver no namespace local não seja o 'cmp' original, caso você utilize algum módulo onde isso acontece o sort não irá funcionar corretamente. Neste caso deveremos importar a função 'cmp' diretamente das funções 'bulti-in' ou melhor do namespace '__builtin__'.

Detalhe sobre a implementação do método sort

O algoritmo de 'sort' utilizado no python é um tando diferente dos algoritmos tradicionais, um tipo de mergesort com algumas coisas a mais.

Retirado dos fontes do python listsort.txt:

"This describes an adaptive, stable, natural mergesort, modestly called timsort (hey, I earned it ). It has supernatural performance on many kinds of partially ordered arrays (less than lg(N!) comparisons needed, and as few as N-1), yet as fast as Python's previous highly tuned samplesort hybrid on random arrays.

In a nutshell, the main routine marches over the array once, left to right, alternately identifying the next run, then merging it into the previous runs "intelligently". Everything else is complication for speed, and some hard-won measure of memory efficiency.
"

terça-feira, julho 25, 2006

Debugando Python com pdb

Programar sem alguma ferramenta para debug pode ser um grande sofrimento.

Poucas pessoas conhecem ou utilizam, mas o Python, possui um módulo de debug incorparado a biblioteca padrão, chamado pdb:

"The module pdb defines an interactive source code debugger for Python programs. It supports setting (conditional) breakpoints and single stepping at the source line level, inspection of stack frames, source code listing, and evaluation of arbitrary Python code in the context of any stack frame. It also supports post-mortem debugging and can be called under program control."

E para alegria de muitos programadores ele cumpre bem o que promete.

Uso básico:

import pdb; pdb.set_trace()

Quando a linha acima for executadao, o pdb toma conta da situação e seu prompt fica disponivel para que o programador possa debugar seu programa da maneria mais tradicional possivel, o 'set_trace' é um ponto de rastreamento ou captura que colocamos em nosso código.

-> Pdb().set_trace()
(Pdb)

Aqui fica intuitivo os comandos são simples 'n - next', 'c - continue' e assim por diante. Para mais detalhes 'help'.

O inconveniente deste processo é que temos que modificar nossos fontes para poder debugar, adicionando a linha de captura (chamada do metodo set_trace). O esquecimento desta linha no código por exemplo, pode fazer com que o programa fique travado.

Para evitar essas situações podemos utilizar o pdb pela linha de comando no estilo do gdb.

python -m pdb programa.py

Deste modo o prompt vai ser invocado e então podemos dizer em qual linha será colocado o break, ' b 10' por exemplo e depois 'c' para rodar até o break.

Infelizmente a opção '-m' só funciona no python 2.4, para versões mais antigas podemos executar o pdb diretamente da biblioteca informando o arquivo fonte como argumento, no meu caso:

python /usr/local/lib/python2.2/pdb.py main.py

Crie um link simbolico para o pdb e seja feliz.

Existe ainda uma versão do pdb extendida chamada pypdb, que me parece bastante interessante, mas também requer python >= 2.4.0. O pypdb funciona ainda mais na linha do gdb, até mesmo sendo um programa binário externo que invoca o código python a ser debugado.

Para informações mais completas:

O'Reillty - Interactive Debugging in Python
Debugando o Python
Using the Python debugger (pdb)