検索ガイド -Search Guide-

単語と単語を空白で区切ることで AND 検索になります。
例: python デコレータ ('python' と 'デコレータ' 両方を含む記事を検索します)
単語の前に '-' を付けることで NOT 検索になります。
例: python -デコレータ ('python' は含むが 'デコレータ' は含まない記事を検索します)
" (ダブルクオート) で語句を囲むことで 完全一致検索になります。
例: "python data" 実装 ('python data' と '実装' 両方を含む記事を検索します。'python data 実装' の検索とは異なります。)
当サイトのドメイン名は " getwebtips.net " です。
トップレベルドメインは .net であり、他の .com / .shop といったトップレベルドメインのサイトとは一切関係ありません。
practical_python_design_patterns

Practical Python Design Patterns - Python で学ぶデザインパターン: The Decorator Pattern - Part. 1 「第7章: デコレーターパターン - 概要」の巻 投稿一覧へ戻る

Published 2022年6月4日20:41 by T.Tsuyoshi

SUPPORT UKRAINE

- Your indifference to the act of cruelty can thrive rogue nations like Russia -

Practical Python Design Patterns - The Decorator Pattern 編

Chapter 7: Decorator Pattern
(第7章: デコレーターパターン - 概要)

Time abides long enough for those who make use of it.
(時はそれを活かす者に十分に長く寄り添う)
- Leonardo da Vinci
Overview
(概要)
プログラミングの経験を重ねてくると、非常に一般的なプログラミングデザインで簡単に解決可能な問題には躓かなくなってきます。そしてそのレベルになると、何か別の「ツール」を欲するようになってくると思います。この章ではそういった「ツール」の1つを取り上げます。
マシンの持つパフォーマンスを最大限に引き出したいのであれば、特定のコードの実行時間を正確に知る必要があります。そのためには、対象とするコード部分を実行する前のシステム時間を保存し、コードを実行し、終了した時点の時間も保存しておく必要があります。そして最後に、終了時間から開始時間を引けばコードの実行時間を計測することができますね。
次のコードはフィボナッチ数 (Fibonacci numbers) の算出時間を計測します:
fib.py
import time

n = 77

start_time = time.perf_counter()
fibPrev = 1
fib = 1

for num in range(2, n):
fibPrev, fib = fib, fib + fibPrev

end_time = time.perf_counter()

print(f'{n} 項のフィボナッチ数 = {fib}')
print(f'{n} 項のフィボナッチ数の算出時間 = {end_time - start_time}s')
出力結果は以下のようになるはずです:
77 項のフィボナッチ数 = 5527939700884757
77 項のフィボナッチ数の算出時間 = 6.299931555986404e-06s
このコードを少し発展させて、算出する項目数を指定できるようにフィボナッチ数を求める部分を関数として実装しましょう:
fib_function.py
import time

def fib(n):
start_time = time.perf_counter()
if n < 2:
return 0

fibPrev = 1
fib = 1

for num in range(2, n):
fibPrev, fib = fib, fib + fibPrev

end_time = time.perf_counter()
print(f'{n} 項のフィボナッチ数の算出時間 = {end_time - start_time}s')

return fib

if __name__ == '__main__':
n = 77
print(f'{n} 項のフィボナッチ数 = {fib(n)}')
このコードでは、実行時間の計測を関数内部で行っています。1つの関数だけの実行時間を計測するだけであればもちろんこれで問題はありませんが、プログラム全体の最適化を行う場合は、そういったケースは稀でしょう。しかし幸いなことに、Python における関数は first-class function (第一級関数: 通常のオブジェクトと同様に他の関数への引数や、関数からの戻り値として利用できる関数) ですから、実行時間計測機能を別の関数として実装することができます:
fib_func_profile_me.py
import time
from typing import Callable

def fib(n: int) -> int:
if n < 2:
return 0

fibPrev = 1
fib = 1

for num in range(2, n):
fibPrev, fib = fib, fib + fibPrev

return fib

def profile_me(f: Callable[[int], int], n: int) -> int:
start_time = time.perf_counter()
result = f(n)
end_time = time.perf_counter()
print(f'{n} 項のフィボナッチ数の算出時間 = {end_time - start_time}s')

return result

if __name__ == '__main__':
n = 77
print(f'{n} 項のフィボナッチ数 = {profile_me(fib, n)}')
こうしておけば、実行時間を計測したい関数がある場合、その関数を「計測関数 (profile_me)」に渡すだけです。しかし、この方法にも制約があります。それは、実行時間を計測したい関数とその関数に渡すパラメータタイプを計測関数側で固定してしまっている、という点です。ただここでも Python の関数が「第一級関数である」という特徴が活きてきます。直接計測関数を呼び出す代わりに、実行時間を計測したい関数をあらかじめセットした計測関数を「ラップした」関数を取得することにします:
base_profiled_fib.py
import time
from typing import Callable, Any

def fib(n: int) -> int:
if n < 2:
return 0

fibPrev = 1
fib = 1

for num in range(2, n):
fibPrev, fib = fib, fib + fibPrev

return fib

def profile_me(f: Callable[[Any], Any], n: Any) -> Any:
start_time = time.perf_counter()
result = f(n)
end_time = time.perf_counter()
print(f'{n} 項のフィボナッチ数の算出時間 = {end_time - start_time}s')

return result

def get_profiled_function(f):
return lambda n: profile_me(f, n)

if __name__ == '__main__':
n = 77
fib_profiled = get_profiled_function(fib)
print(f'{n} 項のフィボナッチ数 = {fib_profiled(n)}')
だいぶ良くなりました。しかしまだ不満があります。システム全体のパフォーマンスを改善するためには、実装している多くの関数で実行時間を計測する必要があります。しかし1つの関数を計測する度に、ラップ関数にその関数をセットし、パラメータをセットし、時間を計測し...、を繰り返すとなるとかなりの「無駄な」時間を消費してしまいます。もっと「良い」方法が必要です。
理想的なことを言えば、実行時間を計測したい関数に「タグ付け」をして後はほったらかしておけば計測時間を「報告」してくれるようにしたいわけです。いちいちセットアップをする必要をなくしたいわけです。でもちゃんと手段は用意されています; それがデコレーターパターン (decorator pattern) です。

この投稿をメールでシェアする

0 comments

コメントはまだありません。

コメントを追加する(不適切と思われるコメントは削除する場合があります)