ホームページ > バックエンド開発 > Python チュートリアル > Python でのデコレータの使用について詳しく学ぶ

Python でのデコレータの使用について詳しく学ぶ

WBOY
リリース: 2016-07-21 14:53:17
オリジナル
965 人が閲覧しました

デコレーターとデコレーターのパターン
まず第一に、デコレーターという言葉を使用すると、多くの心配が生じる可能性があることを誰もが理解する必要があります。これは、『デザイン パターン』という本のデコレーター パターンと混同されやすいためです。この新機能に関しては他の用語もいくつか検討されましたが、デコレータが優先されました。
確かに、Python デコレータを使用してデコレータ パターンを実装できることは事実ですが、これは間違いなくその機能のごく一部であり、少し無駄です。 Python デコレータにとって、これはマクロに最も近いものだと思います。

マクロの歴史
マクロの歴史は非常に長いですが、ほとんどの人はC言語の前処理マクロを使用した経験があるかもしれません。ただし、C 言語のマクロにはいくつかの問題があります。(1) マクロは C 言語には存在しません。(2) マクロの動作が少し奇妙で、C 言語の動作と異なる場合があります。 。
言語自体の一部の要素に対する操作をサポートするために、Java と C# の両方にアノテーションが追加されています。もちろん、それらにはすべていくつかの問題があります。場合によっては、目標を達成するために多くの落とし穴を回避しなければならないことがあります。まだ完成していませんが、これらの注釈機能は、これらの言語のいくつかの固有の機能によって制約されることになります (Martin Fowler が説明した「ディレクション」と同じように)
少し違うのは、私を含む多くの C++ プログラマーが C++ テンプレートの威力を認識しており、すでにこの機能をマクロのように使用していることです。
他の多くの言語にもマクロ関数が含まれていますが、私はそれらについてあまり知りませんが、関数の能力と豊富さの点で Python デコレータは Lisp マクロに非常に似ていると恥ずかしがらずに言いたいと思います。

マクロ目標
マクロを次のように説明するのは過言ではないと思います。マクロは、言語要素自体を操作する機能を提供するためにプログラミング言語に存在します。これはまさに Python デコレータが関数を変更してこのクラスを装飾できることです。複雑なメタクラスと比較して、単純なデコレータが提供されることが多いのはこれが理由かもしれません。
ほとんどのプログラミング言語が提供する自己修正 (メタプログラミング) ソリューションには大きな欠点があります。つまり、制限や制約が多すぎるため、他の言語を書いているような錯覚を与えます。
Python は、Martin Fowler が「可能にする」プログラミング言語と呼ぶものに準拠しています。では、変更操作 (メタプログラミング) を実行したい場合、なぜ「異なる」または「制限的な」言語を考え出す必要があるのでしょうか? Python をコピーして自分で作業を始めてみてはいかがでしょうか?これが Python デコレータでできることです。

Python デコレーターでできること
デコレーターを使用すると、関数またはクラスのコード (ロジック) を「挿入」または「変更」できます。デコレータは、よりシンプルかつ強力であることに加えて、AOP アスペクト指向プログラミングに少し似ていますね。たとえば、メソッドの開始または終了の前に実行したいことを追加します (アクセス許可のチェック、追跡、リソースのロックなど、アスペクト指向プログラミングでの一般的な操作など)。デコレータを使用すると、次のことができます:

リーリー

関数のデコレーター
関数デコレータは通常、次のような修飾デコレータを適用するために関数定義のコードの前に配置されます。

リーリー
コンパイラーがこのコードに到達すると、関数 aFunction がコンパイルされ、コンパイルされた関数オブジェクトが myDecorator に渡され、デコレーターは元の関数 aFunction を置き換える新しい関数オブジェクトを生成します。

では、デコレータ myDecorator のコード実装は何でしょうか?デコレータの入門例のほとんどは関数を記述していますが、クラススタイルのデコレータは関数型デコレータよりも理解しやすく、強力であることがわかります。
確認する必要がある唯一のことは、デコレータによって返されるオブジェクトが関数のように呼び出せるため、クラス デコレータは __call__ を実装する必要があるということです。
装飾者は何をすべきでしょうか?まあ、何でもできますが、通常は、必須ではありませんが、渡された元の関数がどこかで実行されることが期待されます。
リーリー

このコードを実行すると、次の出力が表示されます:

リーリー

请注意,myDecorator的构造器实际是在装饰函数的时候执行的。我们可以在__init__()里面调用函数f,能够看到,在装饰器被调用之前,函数调用f()就已经完成了。另外,装饰器的构造器能够接收被装饰的方法。一般来讲,我们会捕捉到这个函数对象然后接下来在函数__call__()里面调用。装饰和调用是两个非常清晰明了的不同的步骤,这也是我为什么说类似装饰器更简单同时也更强大的原因。
当函数aFunction被装饰完成然后调用的时候,我们得到了一个完全不同的行为,实际上执行的是myDecorator.__call__()的代码逻辑,这是因为”装饰“把原有的代码逻辑用新的返回的逻辑给替换掉了。在我们的例子中,myDecorator对象替换掉了函数aFunction。事实上,在装饰器操作符@被加入之前,你不得不做一些比较low的操作来完成同样的事情:

def foo(): pass
foo = staticmethod(foo)
ログイン後にコピー

因为有了@这个装饰器操作符, 你可以非常优雅的得到同样的结果:

@staticmethod
def foo(): pass
ログイン後にコピー

不过也有不少人因为这一点反对装饰器,不过@仅仅是一个很小的语法糖而已,把一个函数对象传递给另外一个函数,然后用返回值替换原有的方法。
我觉着,之所以装饰器会产生这么大的影响是因为这个小小的语法糖完全改变了人们思考编程的方式。的确,通过将它实现成一个编程语言结构,它将”代码应用到代码上面“的思想带到了主流编程思维层面。

青出于蓝
现在我们实现一下第一个例子。在这里我们将会做一些很常规的事情,并且会使用这些代码:

class entryExit(object):

  def __init__(self, f):
    self.f = f

  def __call__(self):
    print "Entering", self.f.__name__
    self.f()
    print "Exited", self.f.__name__

@entryExit
def func1():
  print "inside func1()"

@entryExit
def func2():
  print "inside func2()"

func1()
func2()

ログイン後にコピー

运行结果是:

Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
ログイン後にコピー

现在我们能够看到,那些被装饰的方法有了“进入”和“离开”的跟踪信息。
构造器存储了通过参数传递进来的函数对象,在调用的方法里,我们用函数对象的__name__属性来展示被调用函数的名称,然后调用被装饰的函数自己。

使用函数作为装饰器
对于装饰器返回结果的约束只有一个,那就是能够被调用,从而它能够合理的替换掉原有的被装饰的那个函数。在上面的这些例子中,我们是将原有的函数用包含有__call__()的对象替换的。一个函数对象同样能够被调用,所以我们可以用函数来重写前一个装饰器的例子,像这样:

def entryExit(f):
  def new_f():
    print "Entering", f.__name__
    f()
    print "Exited", f.__name__
  return new_f

@entryExit
def func1():
  print "inside func1()"

@entryExit
def func2():
  print "inside func2()"

func1()
func2()
print func1.__name__
ログイン後にコピー


函数new_f()嵌套定义在entryExit的方法体里面,当entryExit被调用的时候,new_f()也会顺理成章地被返回。值得注意的是new_f()是一个闭包,捕获了参数变量f的值。
当new_f()定义完成后,它将会被entryExit返回,然后装饰器机制发生作用将结果赋值成被装饰的新方法。
代码print func1.__name__的输出结果是new_f,因为在装饰发生的过程中,原来的方法已经被替换成了new_f,如果对你来说这是一个问题的话,你可以在装饰器返回结果之前修改掉函数的名字:

def entryExit(f):
  def new_f():
    print "Entering", f.__name__
    f()
    print "Exited", f.__name__
  new_f.__name__ = f.__name__
  return new_f
ログイン後にコピー

你可以动态的获取函数的信息包括那些你做的更改,这在python里面非常有用。

带参数的装饰器
现在我们把上面的那个例子简单的改动一下,看看在添加装饰器参数的情况下会发生什么情况:

class decoratorWithArguments(object):

  def __init__(self, arg1, arg2, arg3):
    """
    If there are decorator arguments, the function
    to be decorated is not passed to the constructor!
    """
    print "Inside __init__()"
    self.arg1 = arg1
    self.arg2 = arg2
    self.arg3 = arg3

  def __call__(self, f):
    """
    If there are decorator arguments, __call__() is only called
    once, as part of the decoration process! You can only give
    it a single argument, which is the function object.
    """
    print "Inside __call__()"
    def wrapped_f(*args):
      print "Inside wrapped_f()"
      print "Decorator arguments:", self.arg1, self.arg2, self.arg3
      f(*args)
      print "After f(*args)"
    return wrapped_f

@decoratorWithArguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
  print 'sayHello arguments:', a1, a2, a3, a4

print "After decoration"

print "Preparing to call sayHello()"
sayHello("say", "hello", "argument", "list")
print "after first sayHello() call"
sayHello("a", "different", "set of", "arguments")
print "after second sayHello() call"

ログイン後にコピー

从输出结果来看,运行的效果发生了明显的变化:

Inside __init__()
Inside __call__()
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call
ログイン後にコピー

现在,在“装饰”阶段,构造器和__call__()都会被依次调用,__call__()也只接受一个函数对象类型的参数,而且必须返回一个装饰方法去替换原有的方法,__call__()只会在“装饰”阶段被调用一次,接着返回的装饰方法会被实际用在调用过程中。
尽管这个行为很合理,构造器现在被用来捕捉装饰器的参数,而且__call__()不能再被当做装饰方法,相反要利用它来完成装饰的过程。尽管如此,第一次见到这种与不带参数的装饰器迥然不同的行为还是会让人大吃一惊,而且它们的编程范式也有很大的不同。

带参数的函数式装饰器
最后,让我们看一下更复杂的函数式装饰器,在这里你不得不一次完成所有的事情:

def decoratorFunctionWithArguments(arg1, arg2, arg3):
  def wrap(f):
    print "Inside wrap()"
    def wrapped_f(*args):
      print "Inside wrapped_f()"
      print "Decorator arguments:", arg1, arg2, arg3
      f(*args)
      print "After f(*args)"
    return wrapped_f
  return wrap

@decoratorFunctionWithArguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
  print 'sayHello arguments:', a1, a2, a3, a4

print "After decoration"

print "Preparing to call sayHello()"
sayHello("say", "hello", "argument", "list")
print "after first sayHello() call"
sayHello("a", "different", "set of", "arguments")
print "after second sayHello() call"

ログイン後にコピー

输出结果:

Inside wrap()
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call
ログイン後にコピー

関数デコレーターの戻り値は、元のラップされた関数をラップできる関数である必要があります。つまり、Python は装飾が発生したときに返された関数の結果を取得して呼び出し、それを装飾された関数に渡します。これが、デコレーターの実装で定義された 3 つのネストされた関数の理由です。新しい置き換え機能。
クロージャの性質により、wrap_f() は、クラス デコレータの例のように値 arg1、arg2、および arg3 を明示的に格納しなくても、これらのパラメータにアクセスできます。ただし、これは私が「暗黙的よりも明示的な方が優れている」と感じる例です。関数型デコレータはもう少し合理化されているかもしれませんが、クラス デコレータは理解しやすいため、変更や保守が容易です。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート