ホームページ  >  記事  >  バックエンド開発  >  Python デコレータ - クロージャと関数デコレータ

Python デコレータ - クロージャと関数デコレータ

PHPz
PHPz転載
2023-04-10 14:51:071592ブラウズ

1. クロージャ

デコレータを学ぶ前に、まずクロージャの概念を理解する必要があります。クロージャを形成する際の重要なポイント:

  • 関数のネスト
  • 内部関数を外部関数の戻り値として使用する
  • 内部関数は、外部関数

以下はクロージャを説明するためにリストの平均を計算する場合です:

def make_average():
# 创建一个列表,用来保存数值
nums = []

# 定义一个内部函数,用来计算列表的平均值
def average(n):
# 将数值添加到列表中
nums.append(n)
# 返回平均值
return sum(nums) / len(nums)

return average
  1. まず、関数 make_average;
  2. を定義します。次に、make_average 関数で、値を格納するために内部的に空のリストを定義します。
  3. 再度、リストの平均を計算する内部関数を定義します。
  4. 最後に、この内部関数を戻り値として使用します。外部関数 make_average の値に () を追加しないように注意してください。() を追加するとこの関数が呼び出されることになります。
# 调用外部函数,并将其复制给一个变量,注意:此时返回的是内函数的内存地址
a = make_average()
# 给这个变量加(),就相当于调用了内函数average
print(a(20))
print(a(30))

実行結果は次のとおりです。渡された値が 20 の場合、リストには数値が 1 つだけあるため、計算結果は 20 になります。値 30 が渡された場合、計算結果は 20 になります。 2 つの数値は 20 と 30 であるため、平均の計算結果は 25 になります。

Python デコレータ - クロージャと関数デコレータ

2. デコレータ

1. デコレータの紹介

たとえば、2 つの数値の合計とスコアをそれぞれ計算する次の 2 つの関数があります:

def add(a, b):
"""计算两数之和"""
res = a + b
return res

def mul(a, b):
"""计算两数之积"""
res = a * b
return res

ここで要件があります: 前に「計算開始...」を出力したい各関数の計算が開始されます...」、計算が完了すると「計算が終了します...」が表示されます。関数コードを直接変更することでこの要求を満たすことができますが、これには次の問題が発生します。

    これは後々のメンテナンスに不便です。例えば「計算開始…」ではなく「開始…」と印刷したいのですが、必要ではないでしょうか。再度変更するには;
  1. 違反 オープンクローズ原則 (OCP)、つまりプログラムの設計では、プログラムの拡張機能を開き、プログラムの変更を閉じることが必要です。
  2. したがって、関数コードを直接変更する上記の方法は実行できません。元の機能を変更せずに機能を拡張したいと考えています。例:
  3. def new_add(a, b):
    print("开始计算...")
    r = add(a, b)
    print("计算结束...")
    return r
    
    
    print(new_add(22, 33))
実行結果は次のとおりです:

新しい関数を作成するこの方法では、元の関数は変更されませんが、問題が発生します。深刻な問題: Python デコレータ - クロージャと関数デコレータ

指定された関数のみを拡張でき、他の関数に汎用的に使用することはできません。たとえば、上記の 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がデコレータです。しかし、現在の問題は、装飾された関数がパラメータを取る場合はどうなるかということです。例: Python デコレータ - クロージャと関数デコレータ

def introduce2(name, age):
print(f"我叫{name}, 我今年{age}岁了")

2) 装飾された関数にはパラメータがあります

名前と年齢をデコレータ「wrapper_info」に渡すことはできますが、すべての装飾された関数が名前と年齢のみを持つわけではありません。指定された型、または辞書、リスト、タプルなどが渡される場合があります。つまり、渡されるパラメータの型と数が固定されていない場合はどうすればよいでしょうか?

現時点では、可変長パラメーターを使用する必要があります: (*args, **kwargs)

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}")

実行結果は次のとおりです。

3)装饰器带参数

上述提到的是装饰器,一种是应用于被装饰的函数不带参数,一种是被装饰的函数带参数,那装饰器本身能否带参数呢?比如我定义一个变量,想通过传入不同的值来控制这个装饰器实现不同的功能。答案是肯定的,例如:

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')

运行结果如下:

Python デコレータ - クロージャと関数デコレータ

3.装饰器调用

方式一:以函数方式调用

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 サイトの他の関連記事を参照してください。

声明:
この記事は51cto.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。