デコレータを学ぶ前に、まずクロージャの概念を理解する必要があります。クロージャを形成する際の重要なポイント:
以下はクロージャを説明するためにリストの平均を計算する場合です:
def make_average(): # 创建一个列表,用来保存数值 nums = [] # 定义一个内部函数,用来计算列表的平均值 def average(n): # 将数值添加到列表中 nums.append(n) # 返回平均值 return sum(nums) / len(nums) return average
# 调用外部函数,并将其复制给一个变量,注意:此时返回的是内函数的内存地址 a = make_average() # 给这个变量加(),就相当于调用了内函数average print(a(20)) print(a(30))
実行結果は次のとおりです。渡された値が 20 の場合、リストには数値が 1 つだけあるため、計算結果は 20 になります。値 30 が渡された場合、計算結果は 20 になります。 2 つの数値は 20 と 30 であるため、平均の計算結果は 25 になります。
たとえば、2 つの数値の合計とスコアをそれぞれ計算する次の 2 つの関数があります:
def add(a, b): """计算两数之和""" res = a + b return res def mul(a, b): """计算两数之积""" res = a * b return res
ここで要件があります: 前に「計算開始...」を出力したい各関数の計算が開始されます...」、計算が完了すると「計算が終了します...」が表示されます。関数コードを直接変更することでこの要求を満たすことができますが、これには次の問題が発生します。
def new_add(a, b): print("开始计算...") r = add(a, b) print("计算结束...") return r print(new_add(22, 33))
新しい関数を作成するこの方法では、元の関数は変更されませんが、問題が発生します。深刻な問題:
指定された関数のみを拡張でき、他の関数に汎用的に使用することはできません。たとえば、上記の add 関数は拡張できませんが、mul 関数は拡張できません。 mul 関数を拡張するには、別の拡張関数を作成することしかできません; すべての関数をスコープできる一般的な拡張関数を定義したいと考えているためです。元の関数コードを変更しないこのタイプのユニバーサル関数は、デコレータです。 2. 関数デコレーターデコレーターは本質的には Python 関数またはクラスであり、これを使用すると、コードを変更せずに他の関数またはクラスが関数を追加できるようになります。デコレータの戻り値も関数/クラス オブジェクトです。これは、ログ挿入、パフォーマンス テスト、トランザクション処理、キャッシュ、権限の検証など、横断的な要件を持つシナリオでよく使用されます。 1) 装飾された関数はパラメーターを受け取りません。例:def wrapper_info(func): def inner(): print("开始介绍...") res = func() print("介绍结束...") return res return inner def introduce1(): print("我是周润发,我来自HONG KONG") info = wrapper_info(introduce1) info()
が表示されます。元の関数コードを変更せずに、いくつかの追加関数が元の関数に追加されます。func は変更される関数です。装飾された関数に変数として渡され、他の関数に汎用的に使用できます。このwrapper_infoがデコレータです。しかし、現在の問題は、装飾された関数がパラメータを取る場合はどうなるかということです。例:
def introduce2(name, age): print(f"我叫{name}, 我今年{age}岁了")
def wrapper_info(func): """ 用来对其他函数进行扩展,使其他函数可以在执行前做一些额外的动作 :param func: 要扩展的函数对象 :return: """ def inner(*args, **kwargs): print("开始介绍...") res = func(*args, **kwargs) print("介绍结束...") return res return inner
def introduce3(name, age, city): print(f"我叫{name}, 我今年{age}岁了, 我来自{city}")
上述提到的是装饰器,一种是应用于被装饰的函数不带参数,一种是被装饰的函数带参数,那装饰器本身能否带参数呢?比如我定义一个变量,想通过传入不同的值来控制这个装饰器实现不同的功能。答案是肯定的,例如:
def use_log(level): def decorator(func): def inner(*args, **kwargs): if level == "warn": logging.warning("%s is running by warning" % func.__name__) elif level == "info": logging.warning("%s is running by info" % func.__name__) else: logging.warning("%s is running by other" % func.__name__) return func(*args, **kwargs) return inner return decorator def introduce4(name, age, city): print(f"我叫{name}, 我今年{age}岁了, 我来自{city}") info1 = use_log(introduce4('周星驰', 28, '香港')) info1('info') info2 = use_log(introduce4('周润发', 28, '香港')) info2('warn') info3 = use_log(introduce4('成龙', 28, '香港')) info3('xxx')
运行结果如下:
info3 = wrapper_info(introduce3) info3('刘德华', 28, '香港')
如果是装饰器函数带参数,则调用方式为:
info4 = use_log(introduce4('周星驰', 28, '香港')) info4('info')
即在被装饰函数上方以@符号进行修饰
@wrapper_info def introduce3(name, age, city): print(f"我叫{name}, 我今年{age}岁了, 我来自{city}") introduce3('刘德华', 28, '香港')
如果是装饰器函数带参数,例如上述的use_log,则需要在装饰器中传入参数:
@use_log('info') def introduce4(name, age, city): print(f"我叫{name}, 我今年{age}岁了, 我来自{city}")
在不改变原函数代码的情况下,给原函数增加了一些额外的功能,并且能够通用于其他函数,这样的函数就称作为装饰器。
可以通过传统调用函数的方式进行调用,也可以通过@装饰器的方式调用
以上がPython デコレータ - クロージャと関数デコレータの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。