クロージャとデコレータの疑似実用的な例

Pythonを学び始めたばかりの頃、よく知られているフラスコフレームワークのルートデコレータに大きな印象を受けました。 もちろん、それらをどのように実装できるかを推測しましたが、いつものように、(読み取りではなく)書き込みたいという欲求は、フラスコのソースコードを見る必要性を超えていました。 。 Pythonでのクロージャ、デコレータ、スコープの演習は次のようになります。



def do_something(p): return p @implements(do_something, lambda: not p % 2) def do_mod2_something(p): return p / 2 @implements(do_something, lambda: not p % 3) def do_mod3_something(p): return p / 3 do_something(10) # returns 5 do_something(9) # returns 3 do_something(11) # returns 11
      
      







@implementsデコレータを実装する方法は? そのような実装を実際のプロジェクトのどこかで使用できるかどうかは、特定のプログラムがどのように機能するかを理解するための演習を考案する際にほとんど考慮に入れない問題です。 これは、他のプログラミング言語で発生する関数の一種のオーバーライドのように見えます。



上書きする



データを静的に型付けする言語には、関数の実装を置き換えるような手法があります。 コンパイル中に署名を使用して、呼び出しに適した関数が選択されます。 たとえば、C ++およびJavaでは、この手法を使用して、さまざまなデータ型の引数に複数の関数を実装します。 危機にwhatしているものを完全に理解するために、以下は、関数をC ++で置き換えるほぼ標準的な例です。



 #include <iostream> int sum(int a, int b) { std::cout << "int" << std::endl; return a + b; } double sum(double a, double b) { std::cout << "double" << std::endl; return a + b; } int main(void) { std::cout << sum(1, 2) << std::endl; std::cout << sum(1.1, 3.0) << std::endl; return 0; }
      
      





動的型付けを使用するプログラミング言語では、さまざまなタイプのデータの実装をサポートする必要はほとんどありません。 ただし、引数の値に応じて関数のさまざまな実装を実行する機会がある場合はどうなりますか? たとえば、FSMでは、各ステップで現在の状態を確認し、別の状態に移行する必要があります。 または、非常にプラットフォーム固有の機能の実装において。 if-then-elseのチェーンを使用せずに、Pythonでこれを実装するために何らかの方法で実装できますか?



ほとんどすべてがPythonで実装できるようです。 もちろん、生産性が低下する可能性がないわけではありませんが、クロージャーやデコレーターなどの強力なツールが存在することで、自分の自転車や不健康な空想を実現する余地が広がります。



機能



関数はファーストクラスのオブジェクトです。 これはすべてのPythonプログラミング本に書かれています。 これにより、実行時に関数を作成し、属性を変更し、通常は通常のオブジェクトのように扱うことができます。



このリソースだけでなく、デコレータについても非常に多く書かれているので、このトピックに深く入りたくありません。 クロージャーは、環境を格納する関数オブジェクトです。 実際、装飾された各関数はクロージャーであり、関数コードだけでなく、関数の定義中にデコレーター内に存在していた環境全体も保持します。



 In [1]: def m(p): ...: def s(): ...: return p ...: return s ...: In [2]: x = m(10) In [3]: x.func_closure Out[3]: (<cell at 0x10cd547f8: int object at 0x7f89ab505860>,)
      
      





この例は、関数x()が整数オブジェクトに関する情報を含むことを示しています。 このオブジェクトは、x()関数が存在する限り存在します。



さらに、関数には、定義された環境に関する情報が含まれています。 これを行うには、変更可能な辞書で表されるfunc_globals属性を使用します。 これらの機能は、@ implementsデコレータを実装するために使用されます。



@implements



 def implements(orig_obj, requirements=lambda: False): ...
      
      





デコレータは、呼び出し中に要件条件が満たされた場合、装飾されたオブジェクトorig_objの実装を通知します。 使用例は、記事の冒頭に記載されています。 デコレータの実装では、関数の実装からorig_objを呼び出すことはできませんが、これは関数に属性を追加し、装飾する関数の呼び出し中にそれらをチェックすることで簡単に解決できます。



デコレータの仕組みについて簡単に説明します。 呼び出されると、デコレータはglobals()関数を使用して、グローバル名前空間でorig_objを検索します。 これは、元の関数の呼び出しをorig_wrapperハンドラーに置き換えるために必要です。



次に、__ orig_wrapper__属性の存在を確認することにより、名前で見つかったオブジェクトが元の関数のラッパーかどうかを確認します。 この属性が存在しない場合、置換が実行されます。 __impl__属性は、実装と条件を保存するために置換関数に追加されます。



最初のデコレータが呼び出されるとすぐに、do_somethingは、独自の実装を実行する前にすべての要件条件をチェックし、条件が満たされるとデコードされた関数が呼び出されるように動作を変更します。 実装では、上記の関数属性func_globalsを使用して、ラムダ式が必要なコンテキストで実行されるようにします。



ソースコード@implements
 import functools def implements(orig_obj, requirements=lambda: False): def orig_wrapper(*args, **kwargs): for impl in orig_obj.__impl_lookup__.__impl__: impl[0].func_globals.update(kwargs) impl[0].func_globals.update(dict(zip( orig_obj.func_code.co_varnames, args ))) if impl[0](): return impl[1](*args, **kwargs) return orig_obj(*args, **kwargs) setattr(orig_wrapper, '__orig_wrapper__', True) def impl_wrapper(obj): orig = globals()[orig_obj.__name__] if not hasattr(orig, '__orig_wrapper__'): setattr(orig_wrapper, '__impl__', []) functools.update_wrapper( orig_wrapper, globals()[orig_obj.__name__] ) globals()[orig_obj.__name__] = orig_wrapper setattr(orig, '__impl_lookup__', orig_wrapper) orig = globals()[orig_obj.__name__] orig.__impl__.append((requirements, obj)) # do not change behaviour of the implementation return obj return impl_wrapper
      
      





おわりに



さまざまな実装を整理するためのこのアプローチが便利で「イデオロギー的に」真であるかどうかはわかりませんが、この例の研究と作業は、Pythonのクロージャーとスコープがどのように機能するかを理解する良い練習となりました。



All Articles