それぞれの方法に「利点」があるかどうかを言うのはかなり主観的です。
ただし、内部で何が行われるかをよく理解していると、それぞれの機会に最適な選択肢を選択するのが自然になります。
デコレータ(関数デコレータについて説明します)は、入力パラメータとして関数を受け取る呼び出し可能なオブジェクトです。Pythonにはかなり興味深い設計があり、関数以外に他の種類の呼び出し可能なオブジェクトを作成できます。これを使用して、より保守しやすいコードや短いコードを作成することもできます。
デコレータは、Python2.3で「構文ショートカット」として追加されました。
def a(x):
...
a = my_decorator(a)
それに加えて、私たちは通常、デコレータを「デコレータファクトリ」になりたいいくつかの「callables」と呼びます-この種類を使用する場合:
@my_decorator(param1, param2)
def my_func(...):
...
param1とparam2を使用して「my_decorator」が呼び出されます。次に、「my_func」をパラメータとして、再度呼び出されるオブジェクトが返されます。したがって、この場合、技術的には「デコレータ」は「my_decorator」によって返されるものであり、「デコレータファクトリ」になります。
現在、説明されているデコレータまたは「デコレータファクトリ」は、通常、内部状態を維持する必要があります。最初のケースでは、それが保持するのは元の関数(f
例で呼び出される変数)への参照だけです。「デコレータファクトリ」は、追加の状態変数(上記の例では「param1」と「param2」)を登録したい場合があります。
関数として記述されたデコレータの場合、この余分な状態は、囲んでいる関数内の変数に保持され、実際のラッパー関数によって「非ローカル」変数としてアクセスされます。適切なクラスを作成すると、デコレータ関数(「関数」ではなく「呼び出し可能なオブジェクト」と見なされます)にインスタンス変数として保持でき、それらへのアクセスがより明示的で読みやすくなります。
したがって、ほとんどの場合、どちらのアプローチを好むかは読みやすさの問題です。短くて単純なデコレータの場合、機能的なアプローチは、クラスとして記述されたアプローチよりも読みやすいことがよくありますが、より複雑なアプローチ、特に1つのアプローチもあります。 「デコレータファクトリ」は、Pythonコーディングの前に「フラットはネストよりも優れている」というアドバイスを最大限に活用します。
検討:
def my_dec_factory(param1, param2):
...
...
def real_decorator(func):
...
def wraper_func(*args, **kwargs):
...
#use param1
result = func(*args, **kwargs)
#use param2
return result
return wraper_func
return real_decorator
この「ハイブリッド」ソリューションに対して:
class MyDecorator(object):
"""Decorator example mixing class and function definitions."""
def __init__(self, func, param1, param2):
self.func = func
self.param1, self.param2 = param1, param2
def __call__(self, *args, **kwargs):
...
#use self.param1
result = self.func(*args, **kwargs)
#use self.param2
return result
def my_dec_factory(param1, param2):
def decorator(func):
return MyDecorator(func, param1, param2)
return decorator
更新:デコレータの「純粋なクラス」形式がありません
ここで、「ハイブリッド」メソッドは、最短で読みやすいコードを維持しようとする「両方の長所」を採用していることに注意してください。クラスのみで定義された完全な「デコレータファクトリ」には、2つのクラス、または装飾された関数を登録するために呼び出されたか、実際に最終関数を呼び出すために呼び出されたかを知るための「mode」属性が必要です。
class MyDecorator(object):
"""Decorator example defined entirely as class."""
def __init__(self, p1, p2):
self.p1 = p1
...
self.mode = "decorating"
def __call__(self, *args, **kw):
if self.mode == "decorating":
self.func = args[0]
self.mode = "calling"
return self
# code to run prior to function call
result = self.func(*args, **kw)
# code to run after function call
return result
@MyDecorator(p1, ...)
def myfunc():
...
そして最後に、2つのクラスで定義された純粋な「ホワイトコラー」デコレータ-おそらく物事をより分離したままにしますが、冗長性をより維持しやすいとは言えないほどに増やします。
class Stage2Decorator(object):
def __init__(self, func, p1, p2, ...):
self.func = func
self.p1 = p1
...
def __call__(self, *args, **kw):
# code to run prior to function call
...
result = self.func(*args, **kw)
# code to run after function call
...
return result
class Stage1Decorator(object):
"""Decorator example defined as two classes.
No "hacks" on the object model, most bureacratic.
"""
def __init__(self, p1, p2):
self.p1 = p1
...
self.mode = "decorating"
def __call__(self, func):
return Stage2Decorator(func, self.p1, self.p2, ...)
@Stage1Decorator(p1, p2, ...)
def myfunc():
...
2018年の更新
私は数年前に上記のテキストを書きました。私は最近、「よりフラットな」コードを作成するために好むパターンを思いつきました。
基本的な考え方は関数を使用するpartial
ことですが、デコレータとして使用される前にパラメータを使用して呼び出された場合は、それ自体のオブジェクトを返します。
from functools import wraps, partial
def decorator(func=None, parameter1=None, parameter2=None, ...):
if not func:
# The only drawback is that for functions there is no thing
# like "self" - we have to rely on the decorator
# function name on the module namespace
return partial(decorator, parameter1=parameter1, parameter2=parameter2)
@wraps(func)
def wrapper(*args, **kwargs):
# Decorator code- parameter1, etc... can be used
# freely here
return func(*args, **kwargs)
return wrapper
そして、それだけです。このパターンを使用して記述されたデコレータは、最初に「呼び出される」ことなく、関数をすぐにデコレートできます。
@decorator
def my_func():
pass
またはパラメータでカスタマイズ:
@decorator(parameter1="example.com", ...):
def my_func():
pass
2019 -Python 3.8と位置のみのパラメーターを使用すると、func
引数を位置のみとして宣言でき、パラメーターに名前を付ける必要があるため、この最後のパターンはさらに良くなります。
def decorator(func=None, *, parameter1=None, parameter2=None, ...):