ダイアモンド継承とsuper()
python 2.7 の話。
まずはダイアモンド継承を書く
class A(object): def talk(self): print 'This is A.' class B(A): def talk(self): print 'This is B' class C(A): pass class D(B, C): pass def main(): d = D() d.talk() if __name__ == '__main__': main()
結果は
This is B
継承順序を入れ替えてみる
class D(C, B): # 順序を入れ替えてみる pass def main(): d = D() d.talk()
結果は
This is B
C.talk() = A.talk()
ではないらしい。
super()で C.talk() が呼び出されるように指定してみる
class D(B, C): def talk(self): super(C, self).talk() # C を明示する def main(): d = D() d.talk()
結果は
This is A.
C.talk() = A.talk()
となったみたい。
printでstderrに出力する
今までしらなかったので。
import sys print >> sys.stderr, 'abc'
プロパティの作り方
下の書き方、どちらでもOK。
#!/usr/bin/env python # -*- coding: utf-8 -*- class A(object): def __init__(self): self._x = 0 def get_val(self): return self._x def set_val(self, value): self._x = value value = property(get_val, set_val) class B(object): def __init__(self): self._x = 0 @property def value(self): return self._x @value.setter def value(self, value): self._x = value def main(): a = A() print a.value a.value = 1 print a.value b = B() print b.value b.value = 1 print b.value if __name__ == '__main__': main()
デコレータ
なんの気なしに @classmethod とか @staticmethod とか使っていたけど、なかなかの仕組みらしい。
デコレータとは
(デコレータ) 関数を返す関数。 通常、 @wrapper という文法によって関数を変換するのに利用されます。 デコレータの一般的な利用として、 classmethod() と staticmethod() があります。
デコレータ = ただの関数だという柔軟性にビックリ。
要は必要があれば、自分で定義できちゃうってことだ。
デコレータの制約は
- 関数を引数とする
関数を返す
というシンプルな制約のみ。
だから、デコレータには
- __call__() を持つオブジェクト
- __call__() を持つクラス
を指定することもできる。
追記
関数は返さなくてもよい。property()がそれ。
特に、クラスを指定した場合、
- 指定した関数はコンストラクタの引数にしてオブジェクトが生成される
- 指定した関数を呼び出すと、生成したオブジェクトの __call__() が呼び出される
といった振る舞いになる。
使い道
- State Machine Implementaion
- Singleton
- Synchronization
など実用的な使い道が公開されていた。
デコレータを実行するタイミング
@wrapper の表記で書いたデコレータが実行されるのは、@wrapper が読み込まれた時。
一般的には、関数の【実行時】ではなく、関数の【定義時】にデコレートする。
以下、サンプルコード。
#!/usr/bin/env python # -*- coding: utf-8 -*- def deco_fn(fn): print 'deco_fn' return fn class deco_class(object): def __init__(self, fn): print 'deco_class.__init__()' self._fn = fn def __call__(self, *args): print 'deco_class.__call__()' self._fn(*args) class A(object): @deco_fn def foo(self): print 'foo' @deco_class def boo(): print 'boo' def main(): print '---' print 'execute start.' a = A() a.foo() boo() print 'execute end.' if __name__ == '__main__': main()
で、結果。
deco_fn deco_class.__init__() --- execute start. foo deco_class.__call__() boo execute end.
もー、複雑すぎてワクワクする仕組みだ。
ファイルの読み込みと for~in
当たり前といえば、当たり前なんだけど。
ファイルの読み込みで for~in を途中で break して、また同じファイルに for~in すると続きを読むことができる。
with open(filename, 'r') as source: for line in source: out.write(line) # 1 行目を出力 break; for line in source: out.write(line) # 2 行目以降を出力
ジェネレータ関数に対する for~in でも同じ挙動。
def num(): for n in range(10): yield n def boo(): b = num() for n in b: print n # 0 を出力 break for n in b: print n # 1 以降を出力
listやdict.iter*()などは常に先頭から返しなおす。
def foo(): a = [1, 2, 3] for n in a: print n # 1 を出力 break for n in a: print n # 1 から出力しなおす
def foo(): a = {'abc': 0, 'def': 1, 'xyz': 3} for n in a.iterkeys(): print n # 'abc' を出力 break for n in a.iterkeys(): print n # 'abc' から出力しなおす
Pythonで継承元のメソッドを呼び出すには
伝統的な書き方
どんなメソッドを読んでいるのかが明示的。
class C(B): def method(self, arg): B.method(self, arg)
一時的なクラスオブジェクトを使用する
super() の代替方法。
メリットは継承元の指定を一定に書けるけど、それがよい事かはまた別の話。
base = B class C(base): def method(self, arg): base.method(self, arg) base = None
Python は関数を呼ぶコストがハンパない
テキストファイルを1バイトずつ走査するテストコードを書いてみた。
テストするテキストファイルのサイズは 6,811,956 バイト。
まずは元のソース。
def main(): buffer = '' filei = open('sample.txt', 'r') while True: read_buffer = filei.read(0x2000) if read_buffer == '': break index = 0 length = len(read_buffer) while index < length: buffer += read_buffer[index] index += 1 filei.close() fileo = open('out.txt', 'w') fileo.write(buffer) fileo.close() if __name__ == '__main__': main()
profile した結果、
1622 function calls in 5.103 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 2 0.000 0.000 0.000 0.000 :0(close) 806 0.001 0.000 0.001 0.000 :0(len) 2 0.001 0.000 0.001 0.000 :0(open) 807 0.041 0.000 0.041 0.000 :0(read) 1 0.007 0.007 0.007 0.007 :0(setprofile) 1 0.024 0.024 0.024 0.024 :0(write) 1 0.000 0.000 5.096 5.096 RLE-SA~1.PY:7(<module>) 1 5.029 5.029 5.096 5.096 RLE-SA~1.PY:7(main) 1 0.000 0.000 5.103 5.103 profile:0() 0 0.000 0.000 profile:0(profiler)
ここに空関数を足してみる。
# ↓ コレを追加 def foo(): pass def main(): buffer = '' filei = open('sample.txt', 'r') while True: read_buffer = filei.read(0x2000) if read_buffer == '': break index = 0 length = len(read_buffer) while index < length: buffer += read_buffer[index] index += 1 foo() # ← コレを追加 filei.close() fileo = open('out.txt', 'w') fileo.write(buffer) fileo.close() if __name__ == '__main__': main()
profile した結果はこちら。
6598988 function calls in 14.462 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 2 0.000 0.000 0.000 0.000 :0(close) 806 0.001 0.000 0.001 0.000 :0(len) 2 0.001 0.000 0.001 0.000 :0(open) 807 0.042 0.000 0.042 0.000 :0(read) 1 0.007 0.007 0.007 0.007 :0(setprofile) 1 0.023 0.023 0.023 0.023 :0(write) 1 10.526 10.526 14.454 14.454 RLE-SA~1.PY:10(main) 1 0.000 0.000 14.455 14.455 RLE-SA~1.PY:7(<module>) 6597366 3.861 0.000 3.861 0.000 RLE-SA~1.PY:7(foo) 1 0.000 0.000 14.462 14.462 profile:0() 0 0.000 0.000 profile:0(profiler)
5.103 seconds → 14.462 seconds って…。
プロファイルの結果によると、ざっくり考えても
- 空関数の foo の実行時間は 3.861 seconds
- 関数の呼び出しコストは 5.498 seconds
ってこと。
速度の最適化を考える場合、『ループの中の関数呼び出しを減らす』のは Python には効果てきめんのようだ。