検索ガイド -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 Proxy Pattern - Part. 2 「プロキシパターン - キャッシュプロキシ」の巻 投稿一覧へ戻る

Published 2022年6月10日20:45 by T.Tsuyoshi

SUPPORT UKRAINE

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

Practical Python Design Patterns - The Proxy Pattern 編

The Proxy Pattern - cache proxy
(プロキシパターン - キャッシュプロキシ)

プロキシクラスはオリジナルクラスと同じインターフェースを提供しますが、内部的にはオリジナルオブジェクトへのアクセスを制御します。そしてこの制御の一環として、オリジナルオブジェクトに対するアクセスの前後で追加のタスクを実行します。こういった動作は、クライアントの理解・操作を必要としない (求めるべきではない) 「メモ化」のような機能を実装したい場合にはうってつけです。クライアントと fib メソッドの呼び出しの間に割って入ることで、既に実行済みの呼び出しに対しては「算出値」を返し実際の呼び出しは行わないで済ませることが可能となります。
前回の最後で「インターフェースとして機能するクラスを定義する」と書きましたが、「Python は duck typing (ダックタイピング) 言語である」という特徴がこの手間を省いてくれます。我々はただ単にオリジナルクラスをコピー&ペーストして、そのインターフェースのままプロキシクラスを実装すれば良いだけです:
import time
from functools import wraps


class RawCalculator: # ①
"""
オリジナルクラス
"""

def fib(self, n):
if n < 2:
return 1

return self.fib(n - 2) + self.fib(n - 1)


def memoize(f): # ⑤
"""
キャッシュ機能提供デコレータ
"""

__cache = {}

@wraps(f)
def memoized(*args): # ⑥
key = (f.__name__, args) # ⑦
if key in __cache: # ⑧
return __cache[key]

__cache[key] = f(*args) # ⑨
return __cache[key]

return memoized


class CalculatorProxy: # ②
"""
プロキシクラス
"""

def __init__(self): # ③
self.target = RawCalculator()

fib = getattr(self.target, 'fib')
setattr(self.target, 'fib', memoize(fib))

def __getattr__(self, name): # ④
return getattr(self.target, name)


if __name__ == '__main__':
calculator = CalculatorProxy()

start_time = time.perf_counter()
fib_sequence = [calculator.fib(n) for n in range(1, 41)]
end_time = time.perf_counter()

print(f'{len(fib_sequence)} 回のフィボナッチ数算出所要時間: {end_time - start_time} 秒')
このコードを最初チラッと見たときは??少し戸惑うかもしれません。しかし、このシリーズをここまで読み進めてきた方であれば、デコレータ (decorator) に関しても、__getattr__() 特殊関数に関しても理解できているはずですから、落ち着いて追っていけば「なるほど」と頷けるはずです。では順を追って見ていきましょう。
オリジナル Calculator クラスです。将来的にはフィボナッチ数算出メソッドだけではなく多数の数学計算メソッドを実装します (皆さんにお任せします)。ここで実装しているフィボナッチ数算出メソッドは、この章の最初で実装した最も単純な関数と全く同じです。
これがプロキシクラスになります。
プロキシクラスの初期化時にはオリジナルクラスをインスタンス化し、そのオブジェクトを内部変数として保存します。また、オリジナルクラスオブジェクトの属性辞書から fib メソッドを取得し、キャッシュ機能提供デコレータでラップした上で再度同じ属性名で保存し直しています。これによって、fib() の実行時にキャッシュ機能が有効になります。
プロキシクラスオブジェクトに対する fib() の呼び出しはオリジナルクラスオブジェクトの fib() の呼び出しに変換されますが、このメソッドはキャッシュ機能提供デコレータ memoize() でラップされたものです。
関数を引数に取り関数を返す higher-order function (高階関数) であり、キャッシュ機能を提供するデコレータとして利用します。実行時にはキャッシュ情報を保存しておくための dictionary を定義します。
オリジナル fib() のラッパー関数です。これがキャッシュ機能実現の本丸になりますから少し詳しく見ていきましょう。
キャッシュ辞書 (__cache) の key を作成します。この key は2要素からなるタプルです。最初の要素は実行すべきオリジナル関数名 (この例では 'fib')、2つ目の要素は渡されてきた引数です。例えばクライアントが calculator.fib(10) という呼び出しを行った場合、この key は ('fib', 10) となります。
key がキャッシュに存在するかを確認します。「key がキャッシュに存在する」ということは、渡されてきた引数 (フィボナッチ項目数) でこの関数 (fib) がすでに実行されたことがある、ということです。この場合の key に対する value は、算出した該当項目数でのフィボナッチ数、です。結果として、fib() を呼び出して計算を行うのではなくキャッシュ値を返します。
もし key がキャッシュに存在しなければオリジナル関数 (fib) を実行し、該当項目数のフィボナッチ数を算出します。返されてきた計算値は呼び出し元に返される前にキャッシュ辞書に保存されます。
実行結果は次のようになりました:
40 回のフィボナッチ数算出所要時間: 4.610000178217888e-05
ここで実装した memoize() デコレータ関数は、コードを一切変更することなくあらゆる関数を対象に「メモ化」機能を提供することが可能です。このようなジェネリックなコードが手元にあると非常に便利です。これがオブジェクト指向プログラミング (object-oriented programming) の主要な目的の1つでもあります。
プログラミングの経験を積み重ねてきたら、こういった再利用可能な有用なコードを集めてパッケージやライブラリを作成しておくべきでしょう。そうすれば「より多くのことをより少ない時間」で達成することが可能になります。また「自分の進歩の証」としても励みになりますよね。
さてプロキシパターンは理解できたと思いますから、ここで取り上げたキャッシュプロキシ (cache proxy) 以外の代表的なものをご紹介したいと思います:
Remote proxy
Virtual proxy
Protection proxy
Remote Proxy
(リモートプロキシ)
オブジェクトの存在場所を抽象化したい場合に利用できます。このプロキシを通したオブジェクトの呼び出しはリモートオブジェクトに転送され実行されます。結果が返されるとそれをローカルオブジェクトとして提供しますから、クライアントからすると元のオブジェクト自体がローカルに存在するもの、と映ります。
Virtual Proxy
(バーチャルプロキシ)
あるオブジェクトの作成コストが高く、また、プログラム実行において即座に必要とされるものではない場合に利用できます。このプロキシオブジェクトの作成と同時に該当する作成コストの高いオブジェクト作成も行われるわけではなく、実際にそのオブジェクトが必要とされる呼び出し時に作成を行います。また一旦作成したオブジェクトのキャッシュとしても機能しますから、実際のオブジェクト作成は1度実行されるだけです。
Protection Proxy
(プロテクションプロキシ)
多くのシステムにはアクセスレベルが異なる様々なユーザー権限が設定されています。システムの利用者 (クライアント) とシステムを構成するオブジェクトの間にプロキシを配置し、そのクライアントが所持する権限に基づいて、オブジェクトの情報やメソッドへのアクセスを制限することを目的として利用します。

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

0 comments

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

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