デコレータの力と美しさ

言語の要素を理解し理解するのが最も難しいのはデコレータです。実際、これは非常に単純なものであり、初心者のプログラマでも理解することができます。 私は新しいエベレストをオープンしませんが、可能性の簡単な概要といくつかの典型的な使用例を提供するだけです。 Pythonでのメタプログラミングへの短い脱線。



UPD 1 :デコレータパターンと同じ名前の言語デザインの非類似性に関する若干のカテゴリ記述を、よりソフトなものに変更しました。





最初に、ここでPython言語の要素と見なされるデコレータは、同じ名前のデザインパターンの実装ではなく、その可能性ははるかに広いことに注意してください。パターン自体はPythonデコレータを介して実装できます。




デコレータとは何ですか?それを使用する最も簡単な方法


したがって、デコレータは、いくつかの関数の動作を変更する便利な方法です(Python 2.6および3.0以降、およびクラス全体)。 構文の観点からは、非常に単純に見えます。 たとえば、デコレータを使用した次のコードフラグメント:



 @ f1
 def func(x):パス 




これと同等:



  def func(x):パス
 func = f1(func) 




「同等」という言葉は文字通りに理解する必要があります。操作は、関数が1回定義されたときに実行され、f1が「なし」を返すと、func変数に「なし」が書き込まれます。 簡単な例(装飾関数はNoneを返します。その結果、funcもNoneと等しくなります):



  def empty(f):
    何も返さない

 @empty
 def func(x、y):
     x + yを返します

 print func#印刷されます:なし




より実用的な例を見てみましょう。 特定の機能がどれだけ速く機能するかを確認する必要があるとし、各呼び出しにかかる時間を知る必要があるとします。 このタスクは、デコレータの助けを借りて簡単に解決できます。



輸入時間
 defタイマー(f):
     def tmp(*引数、** kwargs):
         t = time.time()
         res = f(*引数、** kwargs)
         print "関数実行時間:%f"%(time.time()-t)
        返品

     tmpを返す

 @timer
 def func(x、y):
     x + yを返します

 func(1、2)#次のように出力されます:関数実行時間:0.0004 




例からわかるように、実行ごとにfunc関数に実行時間を強制的に出力させるには、タイマーデコレータで「ラップ」するだけです。 「@timer」の行をコメントアウトすると、funcは通常どおり機能し続けます。



タイマー()関数は、最も典型的なデコレータです。 その唯一のパラメーターとして、関数を受け取り、新しい関数(この例ではtmpという名前)を内部で作成します。この関数では、いくつかのロジックを追加し、この新しい関数を返します。 tmp()関数の署名-tmp(* args、** kwargs)注意してください 。これはすべての可能な引数を「キャプチャ」する標準的な方法です。したがって、デコレータは完全に任意の署名を持つ関数に適しています。



関数は、いくつかのデコレータでラップできます。 この場合、それらは上から下へ「実行」されます。 たとえば、関数を実行する前に1秒間一時停止するpause()デコレータを作成します。



 輸入時間

 def pause(f):
     def tmp(*引数、** kwargs):
         time.sleep(1)
         return f(*引数、** kwargs)

     tmpを返す




そしてfunc関数を次のように定義します(一時停止とタイマーの2つのデコレータを同時に使用します):



  @timer
 @pause
 def func(x、y):
     x + yを返します 




ここでfunc(1、2)を呼び出すと、約1秒の合計実行時間が表示されます。



デコレータのより洗練された使用




関数だけがデコレータとして使用できると考えるかもしれません。 そうではありません。 呼び出すことができるオブジェクトはすべて、デコレータとして機能できます。 たとえば、クラスはデコレータとして機能する場合があります。 デコレータを使用してスレッドを構築する方法を示す、はるかに複雑な例を次に示します。



 インポートスレッディング

クラスThread(threading.Thread):
     def __init __(self、f):
         threading.Thread .__ init __(self)
         self.run = f

 @スレッド
 def ttt():
     print「これはスレッド関数です」

 ttt.start() 




この例を詳しく見てみましょう。 スレッドクラスを作成する「クラシック」な方法は次のとおりです。新しいクラスが作成され、threading.Threadクラスの継承者です(スレッドはスレッドを操作するための標準Pythonモジュールです)。 クラス内でrun()メソッドが設定されます。これにより、実行する必要のあるコードが別のスレッドに直接配置され、このクラスのインスタンスが作成されてstart()メソッドが呼び出されます。 「クラシック」バージョンでは次のようになります。



 クラスThreadClassic(threading.Thread):
     def run(self):
         print「これはスレッド関数です」

 ttt = ThreadClassic()
 ttt.start() 




この場合、装飾された関数は引数としてストリームクラスのコンストラクターに渡され、そこで実行クラスのコンポーネントに割り当てられます。



いくつかの異なるスレッドを作成するには、「クラシック」コードを2回複製する必要があります。 また、「ストリーミング」デコレータを使用する場合は、デコレータへの呼び出しをストリーム関数に追加するだけです。



ストリームの例は、情報提供のみを目的としています。 実際には、ここで説明するデコレータですべてのストリーミングコードをラップできるわけではないため、非常に慎重に使用する必要があります。




次の形式のレコードであるデコレーターにパラメーターを渡すことができます。



  @ f1(123)
 def func():パス 


と同等



  def func():パス
 func = f1(123)(func)




本質的に、これはデコレータが関数f1(123)を実行した結果であることを意味します。 ラップされた関数を実行する前に一時停止の量を指定できる更新されたpause()デコレータを作成しましょう。



 輸入時間

 def pause(t):
     defラッパー(f):
         def tmp(*引数、** kwargs):
             time.sleep(t)
             return f(*引数、** kwargs)
         tmpを返す

    ラッパーを返す

 @pause(4)
 def func(x、y):
     x + yを返します

印刷機能(1、2) 


pause()関数内でデコレータが実際に動的に作成される方法に注目してください。



クラスでデコレータを使用する




クラスメソッドでデコレータを使用することは、通常の関数でデコレータを使用することと同じです。 ただし、クラスにはstaticmethodおよびclassmethodという名前の事前定義されたデコレーターがあります 。 これらは、それぞれ静的メソッドとクラスメソッドを定義することを目的としています。 それらの使用例を次に示します。



 クラスTestClass(オブジェクト):
     @classmethod
     def f1(cls):
         print cls .__ name__

     @staticmethod
     def f2():
        合格する

クラスTestClass2(TestClass):
    合格する

 TestClass.f1()#TestClassを出力
 TestClass2.f1()#TestClass2を出力

 a = TestClass2()
 a.f1()#TestClass2を出力




静的メソッド(staticmethodデコレータでラップ)は、基本的にC ++またはJavaの静的メソッドに対応しています。 しかし、クラスメソッドはもっと興味深いものです。 このようなメソッドの最初の引数はクラスです(インスタンスではありません!)、これはクラスのインスタンスへの参照を最初の引数として受け取る通常のメソッドとほぼ同じように起こります。 インスタンスでクラスメソッドが呼び出された場合、現在のインスタンスクラスが最初のパラメーターとして渡されます。これは上記の例で確認できます。生成されたクラスの場合、生成されたクラスが転送されます。



デコレータは他にどこで使用できますか?


デコレータの潜在的なアプリケーションのリストは非常に長いです。



本当に難しい




デコレータは非常に慎重に使用する必要があり、何を達成したいかを正確に把握する必要があります。 それらを過度に使用すると、コードが複雑になりすぎて理解できなくなります。 あなたは洞察の攻撃で書くことができ、あなた自身が後でどのように書かれたのかを理解しないでしょう。



デコレータを使用すると、メソッド/関数のドキュメント文字列が壊れます。 この問題は、__ doc__値をデコレータ内で作成された関数に手動で「転送」することで解決できます。 また、予期しない名前decoratorで素晴らしいモジュールを使用できます。これは、doc文字列のサポートに加えて、他の多くの便利なことを実行できます。



推奨読書


  1. http://www.phyast.pitt.edu/~micheles/python/documentation.html-デコレーターモジュールドキュメントページ
  2. www.ibm.com/developerworks/linux/library/l-cpdecor.html-IBM developerWorksのデコレーターに関する記事(およびロシア語への翻訳
  3. Mark Lutz:Pythonの学習、第3版、「関数デコレータ」の章
  4. PEP 318:関数デコレーター
  5. PEP 3129:クラスデコレーター (Python 2.6およびPython 3.0以降)

    wiki.python.org/moin/PythonDecorators-デコレーターに関する公式Python wikiの記事




All Articles