Bem que você tentou, releu o código diversas vezes, escreveu um monte de prints, mas não conseguiu descobrir o que está causando aquele bug indesejado. É chegada a hora de debugar seu código, a seguir darei algumas dicas de como debugar com pdb seu projeto em Django[1].
Primeiramente, debugar um projeto em Python[2] e debugar um projeto em Django não tem muita diferença, a coisa toda é muito parecida, você só precisa do pontapé inicial para se virar dali pra frente.
Para aprender como utilizar o pdb[3] (vulgo Python Debugger) crie um novo projeto chamado djdebug, logo em seguida crie um arquivo vazio chamado views.py na própria raiz do projeto, ficando com a seguinte estrutura de arquivos:
djdebug - __init__.py - manage.py - settings.py - urls.py - views.py
No arquivo views.py cole o seguinte código:
from django.shortcuts import HttpResponse
def soma(a,b):
arg1 = a
arg2 = b
soma = arg1 + arg2
return soma
def hello(request):
a = 2
b = 3
c = [1,2,3]
c.pop()
d = soma(3,4)
response = HttpResponse("Hello World")
return response
Como podemos ver o arquivo contém uma função soma, que não faz nada de muito complexo e uma função hello que retorna uma response com o conteúdo "Hello World".
No arquivo urls.py importe a view hello e adicione uma URL para podemos acessá-la:
from views import hello
urlpatterns = patterns('',
...
(r'^hello', hello),
)
Execute o servidor de testes, navegue até http://localhost:8000/hello e verifique que está tudo funcionando:
./manage runserver
Suponha que há um bug em alguma parte da view hello e então adicione o código para possibilitar o debug:
def hello(request):
a = 2
import pdb
pdb.set_trace()
...
Apenas para esclarecer, o import pdb importa o Python debugger, já o pdb.set_trace() serve pra colocar um breakpoint no código, há maneiras de se colocar um breakpoint no código sem alterá-lo através do próprio pdb[3].
Execute o servidor de testes mas desta vez adicione o parâmetro necessário para evitar recarregamento automático do código-fonte:
./manage runserver --noreload
Abra novamente a view hello no seu navegador, ela ficará parada e no terminal que você iniciou o servidor o pdb[3] estará parado esperando por comandos:
> /home/.../djdebug/views.py(13)hello() -> b = 3 (Pdb)
Vamos começar, temos um comando interessante que é o l (de list, lista o código na região do debugger) que lista onde se encontra o debugger:
(Pdb) l
8
9 def hello(request):
10 a = 2
11 import pdb
12 pdb.set_trace()
13 -> b = 3
14 c = [1,2,3]
15 c.pop()
16 d = soma(3,4)
17 response = HttpResponse("Hello World")
18 return response
É importante notar que o debugger se encontra no exato momento antes de executar a linha apontada pela seta, isto é, se executarmos p (de print, imprime a expressão passada como argumento) para inspecionarmos a variável b, ela não estará definida:
(Pdb) p a
2
(Pdb) p b
*** NameError: NameError("name 'b' is not defined",)
Vamos para próxima linha com n (de next, segue para a próxima linha):
(Pdb) n > /home/.../djdebug/views.py(14)hello() -> c = [1,2,3] (Pdb) p b 3
Agora sim b já possui valor, vamos avançar até a chamada da função somar:
(Pdb) n > /home/.../djdebug/views.py(15)hello() -> c.pop() (Pdb) <enter> > /home/.../djdebug/views.py(16)hello() -> d = soma(3,4)
A partir do primeiro n, enter executará n novamente. Como nós não sabemos se o problema da view hello é a função somar, vamos entrar na função somar com s (de step into, entra na função, ou seja, passo a dentro) e verificar se está tudo certo:
(Pdb) s --Call-- > /home/.../djdebug/views.py(3)soma() -> def soma(a,b): (Pdb) l 1 from django.shortcuts import HttpResponse 2 3 -> def soma(a,b): 4 arg1 = a 5 arg2 = b 6 soma = arg1 + arg2 7 return soma 8 9 def hello(request): 10 a = 2 11 import pdb
Como podemos ver o debugger se encontra na entrada da função soma, damos uma “olhada por cima” e saímos da função com up (de up, sobe uma chamada na pilha):
> /home/.../djdebug/views.py(4)soma() -> arg1 = a (Pdb) n > /home/.../djdebug/views.py(5)soma() -> arg2 = b (Pdb) n > /home/.../djdebug/views.py(6)soma() -> soma = arg1 + arg2 (Pdb) u > /home/.../djdebug/views.py(16)hello() -> d = soma(3,4)
Como podemos ver, voltamos a view hello, vamos avançar até antes de retornar a resposta da requisição e atestar umas coisinhas:
(Pdb) n
> /home/.../djdebug/views.py(17)hello()
-> response = HttpResponse("Hello World")
(Pdb) n
> /home/.../djdebug/views.py(18)hello()
-> return response
(Pdb) dir(response)
['__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_charset', '_container', '_convert_to_ascii', '_get_content', '_headers', '_is_string', '_set_content', 'close', 'content', 'cookies', 'delete_cookie', 'flush', 'get', 'has_header', 'items', 'next', 'set_cookie', 'status_code', 'tell', 'write']
(Pdb) response.content
'Hello World'
(Pdb) response.content = 'Django debugado'
(Pdb) c
Como podemos verificar, o pdb[3] fornece um shell do Python[2] você pode executar dir, verificar valores de variáveis e até mudar conteúdo de variáveis (poder e perigo andando juntos), por fim executamos c (de continue, para continuar a execução), veja o resultado no seu navegador, a mensagem "Hello World" foi trocada por "Django debugado".
Recomendo a leitura do paper “Debugging in Python”[4] para mais alguns detalhes do pdb[3]. É muito interessante saber “brincar” com o shell do Python e do Django, com essas ferramentas fica mais fácil rastrear o chato do bug.
[1] - http://www.djangoproject.com/
[2] – http://www.python.org/
[3] – http://docs.python.org/library/pdb.html
[4] – http://www.ferg.org/papers/debugging_in_python.html
Opa ! Beleza..
Obrigado pela dica ! Vai ser muito bem aproveitada…
Muito bom o post Danilo!
Uma dica: ipdb
http://pypi.python.org/pypi/ipdb/0.1dev-r1716
pdb usando ipython