この投稿は 「Python Advent Calendar 2014 - Qiita」 の 17日目の記事です。
Python のコードをデバッグするには、Python標準のデバッガである「pdb」モジュールを使いますが、使い方(ショートカット)がなかなか覚えられず、いつもネットで探してしまうことがありませんか?
そこで、pdb のショートカットをドラクエ風に覚えると絶対忘れないよね、というのを紹介してみたいと思います。
先に、まとめておきます。
ショートカット | 覚え方 | 効能 | 備考 |
ワ (w) ールドマップ | 自分の今いる場所を知りたいときに使う | ||
たいまつ (l の形が似ている) | 実行中のコードの周りを明るく照らす | ||
し (s) らべる | 気になったところ(関数やメソッド)を調べる(もぐり込む) | ||
に (n) げる | 逃げる。前に逃げる | ||
リ (r) レミト | 関数やメソッドの出口まで一瞬でワープ | ||
キ (c) メラの翼 | 元の場所まで一瞬でワープ |
では早速、Django アプリケーションをデバッグ していきましょう。
1. Django アプリケーションを準備する
確認環境は、
- Ubuntu 12.04 (on Vagrant + VirtualBox)
- Python 2.7.3
- Django 1.4.16 LTS
です。
python-pip や virtualenv、git は事前にインストールしておきます。
事前準備は、
を参考に。
今回は、mkvirtualenv (virtualenvwrapper) を使わないパターンで。
$ virtualenv ~/django_env $ source ~/django_env/bin/activate (django_env) $ sudo mkdir -p /opt/webapps (django_env) $ sudo chown `whoami`. /opt/webapps (django_env) $ cd /opt/webapps/ (django_env) $ pip install django==1.4.16
ここで、「django-admin.py startproject mysite」で Djangoアプリケーションを作成する代わりに、Django 1.4 公式チュートリアルの Pollアプリケーション(とほぼ同じもの)が実装されている、
https://github.com/Chive/django-poll-app.git
(の一部不具合を修正した Fork リポジトリ)を使うことにします。
(django_env) $ git clone https://github.com/akiyoko/django-poll-app.git mysite (django_env) $ cd mysite/
なお、データが 1件だけ入ったデータベース (mysite/db.sqlite3) がリポジトリに含まれているので、起動前に「python manage.py syncdb」を実行する必要はありません。
(ちなみに、superuser は「admin/admin」)
2. pdb をコードに仕込む
デバッグをしたいポイントに、以下のコードを仕込みます。
import pdb; pdb.set_trace()
例えば、vote() の中に pdb を仕込んでみます。
polls/views.py
40 def vote(request, poll_id): 41 import pdb; pdb.set_trace() 42 p = get_object_or_404(Poll, pk=poll_id) 43 try: 44 selected_choice = p.choice_set.get(pk=request.POST['choice']) 45 except (KeyError, Choice.DoesNotExist): 46 # Redisplay the poll voting form. 47 return render(request, 'polls/detail.html', { 48 'poll': p, 49 'error_message': "You didn't select a choice.", 50 }) 51 else: 52 selected_choice.votes += 1 53 selected_choice.save() 54 # Always return an HttpResponseRedirect after successfully dealing 55 # with POST data. This prevents data from being posted twice if a 56 # user hits the Back button. 57 return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))
アプリケーションを起動します。
$ python manage.py runserver 0.0.0.0:8000
3. デバッグポイントで停止させる
ブラウザを起動し、「http://192.168.33.10:8000/polls/1/」にアクセスして「Vote」ボタンを押下すると、
pdb を仕込んだ箇所でデバッガが停止します。
> /opt/webapps/mysite/polls/views.py(42)vote() -> p = get_object_or_404(Poll, pk=poll_id) (Pdb)
さあ、ここからが本番です。
ここからは、ダンジョンにいる という体で話を進めますよー。
4. 現在いる場所はどこ?
(ワールドマップ)
最初に、ワールドマップで現在いる場所を見てみましょう。
(Pdb) w
(Pdb) w /usr/lib/python2.7/threading.py(524)__bootstrap() -> self.__bootstrap_inner() /usr/lib/python2.7/threading.py(551)__bootstrap_inner() -> self.run() /usr/lib/python2.7/threading.py(504)run() -> self.__target(*self.__args, **self.__kwargs) /usr/lib/python2.7/SocketServer.py(582)process_request_thread() -> self.finish_request(request, client_address) /usr/lib/python2.7/SocketServer.py(323)finish_request() -> self.RequestHandlerClass(request, client_address, self) /opt/webapps/django_env/local/lib/python2.7/site-packages/django/core/servers/basehttp.py(139)__init__() -> super(WSGIRequestHandler, self).__init__(*args, **kwargs) /usr/lib/python2.7/SocketServer.py(638)__init__() -> self.handle() /usr/lib/python2.7/wsgiref/simple_server.py(124)handle() -> handler.run(self.server.get_app()) /usr/lib/python2.7/wsgiref/handlers.py(85)run() -> self.result = application(self.environ, self.start_response) /opt/webapps/django_env/local/lib/python2.7/site-packages/django/contrib/staticfiles/handlers.py(67)__call__() -> return self.application(environ, start_response) /opt/webapps/django_env/local/lib/python2.7/site-packages/django/core/handlers/wsgi.py(241)__call__() -> response = self.get_response(request) /opt/webapps/django_env/local/lib/python2.7/site-packages/django/core/handlers/base.py(111)get_response() -> response = callback(request, *callback_args, **callback_kwargs) > /opt/webapps/mysite/polls/views.py(42)vote() -> p = get_object_or_404(Poll, pk=poll_id)
スタックトレース??
何だそりゃ??って感じですが、よくよく見てみると、矢印(->)の付いていない「フレーム」(コードの場所)と矢印(->)の付いている「コード」がペアになっているのが分かるかと思います。
ひとつ上の「フレーム」「コード」とその下の「フレーム」「コード」は呼び出し元・呼び出し先の関係になっていて、大元から順に上から出力されています。
「>」が現在のフレームを表しているので、そのすぐ下のコード
-> p = get_object_or_404(Poll, pk=poll_id)
をデバッグすることができる、というわけです。
ここで、(UP)や (DOWN)を叩いてみましょう。フレームが上下に移動しますよね。
(Pdb) d *** Newest frame
と表示されたら、すでに一番下のフレームまで来ているということを表しています。
(たいまつ)
ワールドマップだけでは周りの状況がよく分からないので、実行中のコードの周囲を照らします。
(Pdb) l
「l」が何かの形に見えませんか? そう、「たいまつ」ですよね!
たいまつを使うと、周りのコードが明るく照らされます。
(Pdb) l 37 template_name = 'polls/results.html' 38 39 40 def vote(request, poll_id): 41 import pdb; pdb.set_trace() 42 -> p = get_object_or_404(Poll, pk=poll_id) 43 try: 44 selected_choice = p.choice_set.get(pk=request.POST['choice']) 45 except (KeyError, Choice.DoesNotExist): 46 # Redisplay the poll voting form. 47 return render(request, 'polls/detail.html', {
「->」が付いているのが、現在実行しようとしているコードです。上の例では、
-> p = get_object_or_404(Poll, pk=poll_id)
のところにいるのが分かりますよね。
5. 気になった関数やメソッドを調べたい
気になった関数やメソッドを調べるには、
(しらべる)
そう、「しらべる」ですよね。
(Pdb) s
「->」が付いているコード中の関数やメソッドの中を「しらべる」(もぐり込む)ことができます。
例えば、
-> p = get_object_or_404(Poll, pk=poll_id)
にいるときに (しらべる)を叩くと、
(Pdb) s --Call-- > /opt/webapps/django_env/local/lib/python2.7/site-packages/django/shortcuts/__init__.py(100)get_object_or_404() -> def get_object_or_404(klass, *args, **kwargs): (Pdb)
get_object_or_404() の内部にもぐり込むことができました。
6. コードを先へ進める
現在いる場所が目的の地点ではなかった場合は、どんどん先へ進めていきましょう。
そんなときは、
(にげる)
しかない!!
(Pdb) n
(Pdb) s --Call-- > /opt/webapps/django_env/local/lib/python2.7/site-packages/django/shortcuts/__init__.py(100)get_object_or_404() -> def get_object_or_404(klass, *args, **kwargs): (Pdb) n > /opt/webapps/django_env/local/lib/python2.7/site-packages/django/shortcuts/__init__.py(111)get_object_or_404() -> queryset = _get_queryset(klass) (Pdb) l 106 arguments and keyword arguments are used in the get() query. 107 108 Note: Like with get(), an MultipleObjectsReturned will be raised if more than one 109 object is found. 110 """ 111 -> queryset = _get_queryset(klass) 112 try: 113 return queryset.get(*args, **kwargs) 114 except queryset.model.DoesNotExist: 115 raise Http404('No %s matches the given query.' % queryset.model._meta.object_name) 116 (Pdb)
「->」が一行進みましたね。
(たいまつ)を使うと、現在位置が逐一確認できて分かりやすいですね。
普段は、
(Pdb) n (Pdb) l (Pdb) n (Pdb) l ・ ・
と、一行ずつ進んでは実行中のコードを確認するのがよいでしょう。
7. 出口までワープ
慣れてきたら、一気に出口のところまでワープしてみます。
ダンジョンの出口まで一瞬で出る呪文、そう、
(リレミト)
ですね。
(Pdb) r
(リレミト)を唱えると関数やメソッドの出口まで来るので、この状態で (にげる)を 1回実行すると、関数やメソッドを脱出することができます。
(Pdb) l 106 arguments and keyword arguments are used in the get() query. 107 108 Note: Like with get(), an MultipleObjectsReturned will be raised if more than one 109 object is found. 110 """ 111 -> queryset = _get_queryset(klass) 112 try: 113 return queryset.get(*args, **kwargs) 114 except queryset.model.DoesNotExist: 115 raise Http404('No %s matches the given query.' % queryset.model._meta.object_name) 116 (Pdb) r --Return-- > /opt/webapps/django_env/local/lib/python2.7/site-packages/django/shortcuts/__init__.py(113)get_object_or_404()-><Poll: What's up?> -> return queryset.get(*args, **kwargs) (Pdb) n > /opt/webapps/mysite/polls/views.py(43)vote() -> try: (Pdb) l 38 39 40 def vote(request, poll_id): 41 import pdb; pdb.set_trace() 42 p = get_object_or_404(Poll, pk=poll_id) 43 -> try: 44 selected_choice = p.choice_set.get(pk=request.POST['choice']) 45 except (KeyError, Choice.DoesNotExist): 46 # Redisplay the poll voting form. 47 return render(request, 'polls/detail.html', { 48 'poll': p, (Pdb)
ハイ、脱出しましたね。
8. 変数を表示
ここでデバッグらしく、変数を表示してみましょう。
ドラクエじゃないので覚えにくい ですが、(PRINT)を使います。
(Pdb) l 38 39 40 def vote(request, poll_id): 41 import pdb; pdb.set_trace() 42 p = get_object_or_404(Poll, pk=poll_id) 43 -> try: 44 selected_choice = p.choice_set.get(pk=request.POST['choice']) 45 except (KeyError, Choice.DoesNotExist): 46 # Redisplay the poll voting form. 47 return render(request, 'polls/detail.html', { 48 'poll': p, (Pdb) p poll_id u'1' (Pdb) p request.__class__ <class 'django.core.handlers.wsgi.WSGIRequest'> (Pdb) p p <Poll: What's up?> (Pdb) p selected_choice *** NameError: NameError("name 'selected_choice' is not defined",)
「->」の時点で初期化されていない変数は、NameError になります。
(あと、「p」という変数があるので、あまりよくない例だったかもしれません。。)