概要

前回(『Effective Python』その7 協働作業)のつづきね。8章のノート。

このみろりHPはさくら VPS というサーバに載っけている。その場合、ぼくのぱそこが開発環境で、 VPS が本番環境、という呼び方になるようだ。 VPS にデプロイするときは色々苦労があったので、この章はちょっと楽しみだった。けれどぼくが困ったようなポイントについては触れてなかったな。

 

楽しめたところノート

その「1」、数値? 文字列?

print() より print(repr()) を使おう。数値なのか文字列なのかわかるぞ!

print('1')          # 1
print(repr('1'))    # '1'

 

<__main__.MidoriClass object at 0x000001E0BF22F470> こんなのクソの役にも立たないんだよ

クラスに __repr__ を実装しなかった場合。

class MidoriClass:
    def __init__(self, a):
        self.a = a

instance = MidoriClass(1)
print(instance)
<__main__.MidoriClass object at 0x000001E0BF22F470>

↑こんなのクソの役にも立たないんだよ! ↓こうするんや。

class MidoriClass:
    def __init__(self, a):
        self.a = a
    def __repr__(self):
        return f'MidoriClass({self.a})'

instance = MidoriClass(1)
print(instance)
MidoriClass(1)

見やすいー!! __repr__ で返す文字列は、そのまま eval 関数に渡せる形式にするのがいいらしいよ。

 

Python プログラムに確信を持てるたったひとつの冴えたやりかた unittest

Python には静的型チェックがないから、テストコードを書いてバグを見つけることが大事なんだって。そのために組み込みモジュールの unittest を使いましょうって話だ。ここで紹介されている unittest, nose モジュールはすでに触ったことがあるな。(Python ユニットテストノート)

でも当時しらなかったこととして……

  • テストケースは TestCase のサブクラスにまとめる
  • テストメソッドはその中に書き、メソッド名は test ではじまる名前にする
from unittest import TestCase, main
from utils import to_str

class UtilsTestCase(TestCase):
    def test_to_str_bytes(self):
        self.assertEqual('hello', to_str(b'hello'))

こんな感じね。でもぼくは思うんですけれど、型はあったほうがいいと思うな……。大多数の人は簡潔性と単純さからくる生産性の利得のために、型がないことを評価しているらしいけど。

 

対話式デバッガでブイブイいわせる

コードの中に

import pdb; pdb.set_trace()

を置くと、プログラムの実行がここで一度止まって、対話式のデバッグが始まるんだって。変数の中身を見ることもできるし、 locals() を使えばローカル変数リストの一覧を見ることもできる。便利だねー、ってちょっとまて。そんなの本番では消さないといけないから不便だし、 IDE を使えばもっと簡単に対話式デバッグができるじゃん。

ここではむしろ、 Python を書くのに適した IDE を紹介してくれるべきなんじゃないか? まあ、そういう IDE も内部的にはこういう機能を使ってるのかもなー、って分かったことはよかった。

 

プロファイラで、関数の呼び出し回数と実行時間をゲットする

いや、お恥ずかしながら、これまでプログラムの実行時間を測るときこんなのを使っていたのですよ。

# 処理時間計測スクリプト。

import time

start = time.time()
# ================================
# 処理、ここから。


def foo():
    time.sleep(2)


foo()


# 処理、ここまで。
# ================================
margin = time.time() - start
print(f'結果: {margin}秒')

これにより foo() の実行時間を測れる。↓2秒待つ関数だから、時間はもちろん2秒だ。

結果: 2.000128984451294秒

実はこれは↓のように書くべきだったのだよ。

import time
import cProfile
import pstats


def foo():
    time.sleep(2)


test = lambda: foo()
profiler = cProfile.Profile()
profiler.runcall(test)
stats = pstats.Stats(profiler)
stats.strip_dirs()
stats.sort_stats('cumulative')
stats.print_stats()
      4 function calls in 2.000 seconds

Ordered by: cumulative time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    2.000    2.000 py2.py:10(<lambda>)
     1    0.000    0.000    2.000    2.000 py2.py:6(foo)
     1    2.000    2.000    2.000    2.000 {built-in method time.sleep}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  • ncalls : 関数が呼ばれた回数。
  • tottime: 関数実行に費やした秒数 - 内部で他の関数を実行してた時間。
  • percall: tottime/ncalls のこと。
  • cumtime: 関数実行に費やした秒数。
  • percall: cumtime/ncalls のこと。

えっ、スゴ……(トゥンク)。2秒かかっていることはもちろん、「foo 全体で2秒」「実際に2秒とっているのは foo の中の time.sleep」ということも全部教えてくれている。

これを使うことで、処理のどこで時間がかかっているのか、どこがボトルネックなのか突き止め、適切な部分を最適化するのだ! これは正直おもしろい。以前、プログラミング問題で速さをもとめていたときは、どの関数が何度呼び出されているのか、毎回 print して突き止めていたのだ。かなりアナログなことをしていたようだ。

テストコードに加えて、プロファイルを取得するコードも書くようにしてもいいのかもしれない。

 

Effective Python 感想文一覧

おしまい! 2018年のアタマから読み始めた Python 本だったけれど、読み終えられてよかった。