Pythonのデコレーターの知識ポイントをまとめます。

WBOY
リリース: 2022-06-17 13:50:40
転載
2386 人が閲覧しました

この記事では、python に関する関連知識を提供します。主に、クロージャ、デコレータ、複数のデコレータの使用、パラメータなど、デコレータに関する関連問題を紹介します。デコレータとその他の内容については、以下で見てみましょう。 . 皆様のお役に立てれば幸いです。

Pythonのデコレーターの知識ポイントをまとめます。

推奨される学習: Python ビデオ チュートリアル

1. 終わりに

Decorator とは何かを理解するには (デコレータ) では、まず クロージャ (クロージャ) の概念を知る必要があります。

クロージャ (クロージャ関数または閉じた関数とも呼ばれます) は、一般的に、関数がオブジェクトとして返され、外部変数も同伴すると、クロージャが形成されます。

Hello World の印刷を例として、ネストされた関数の構造がどのようになるかを見てみましょう:

def print_msg(msg):

    def printer():
        print(msg)

    printer()print_msg('Hello World')# Hello World
ログイン後にコピー
Execution

print_msg('Hello World') printer()を実行したこと、つまりprint(msg)を実行したことと同じなので、Hello Worldが出力されます。

これがクロージャである場合にどのような構造になるかを見てみましょう:

def print_msg(msg):

    def printer():
        print(msg)

    return printer


my_msg = print_msg('Hello World')my_msg()# Hello World
ログイン後にコピー
この例の

printer 関数はクロージャです。

Execution

print_msg('Hello World') は実際には次のような関数を返します。これは外部変数を同伴します 'Hello World':

def printer():
    print('Hello World')
ログイン後にコピー
したがって、

my_msg の呼び出しは、printer() の実行と同じです。


では、関数がクロージャ関数であるかどうかを判断するにはどうすればよいでしょうか?クロージャ関数の

__closure__ 属性は、すべてのセル オブジェクトを格納するためのタプルを定義します。各セル オブジェクトは、すべての外部変数をクロージャに格納します。通常の関数の __closure__ 属性は None です。

def outer(content):

    def inner():
        print(content)

    return innerprint(outer.__closure__)
    # Noneinner = outer('Hello World')print(inner.__closure__)
    # (<cell at 0x0000023FB1FD0B80: str object at 0x0000023FB1DC84F0>,)
ログイン後にコピー

outer 関数はクロージャではなく、inner 関数はクロージャであることがわかります。

クロージャによって運ばれる外部変数を表示することもできます:

print(inner.__closure__[0].cell_contents)# Hello World
ログイン後にコピー

ここまでお話しましたが、クロージャは何に役立つのでしょうか?クロージャの存在意義は、外部変数(プライベートグッズ)を運ぶことにあり、プライベートグッズを持たない場合は、通常の関数と変わりません。

クロージャの利点は次のとおりです:

    ローカル変数は共有したり長期間保存したりすることができませんが、グローバル変数は変数汚染を引き起こす可能性があります。地球規模の汚染。
  • クロージャーを使用すると、関数内のローカル変数の値を常にメモリ内に残すことができ、外部関数の呼び出し後に自動的にクリアされなくなります。
2. デコレータ

まず、そのようなシナリオを考えてみましょう。以前に作成した関数が 4 つの関数を実装していると仮定します。簡単にするために、

print ステートメントを使用して、それぞれの特定の関数を表します:

def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')
ログイン後にコピー
さて、何らかの理由で、

関数 5module 関数に追加する必要があります。これは次のように変更できます。

def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')
    print('功能5')
ログイン後にコピー
しかし、実際のビジネスでは、このような変更を直接行うのは危険なことがよくあります (保守が困難になります)。では、

元の関数を変更せずに新しい関数を追加するにはどうすればよいでしょうか?

以前のクロージャの知識を使用することを考えたかもしれません:

def func_5(original_module):

    def wrapper():
        original_module()
        print('功能5')

    return wrapper
ログイン後にコピー

func_5 は、関数が主に function 5## を実装するために使用されることを意味します。 # 、次に module を渡して効果を観察します: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">new_module = func_5(module)new_module()# 功能1# 功能2# 功能3# 功能4# 功能5</pre><div class="contentsignin">ログイン後にコピー</div></div> 新しいモジュール:

new_module

function 5 # を実装していることがわかります。 ##。 上記の例では、関数

func_5
はデコレータであり、元のモジュールを装飾します (新しい関数を追加します)。

もちろん、Python にはより簡潔な記述方法 (構文シュガーと呼ばれる) があり、@ 記号をデコレーター関数の名前とともに使用し、それを関数の定義に配置することができます。装飾済み 上:

def func_5(original_module):

    def wrapper():
        original_module()
        print('功能5')

    return wrapper@func_5def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5
ログイン後にコピー

これに基づいて、次のように、元の関数を変更せずにタイミング タスク (元の関数の実行時間の計算) を完了できます。実際、

my_list
はリストではありません。直接印刷すると、

None

と表示されます。これは、

wrapper 関数が戻り値を設定しないためです。 make_list の戻り値を取得する必要がある場合は、wrapper 関数を次のように変更できます:

def timer(func):

    def wrapper():
        import time
        tic = time.time()
        func()
        toc = time.time()
        print('程序用时: {}s'.format(toc - tic))

    return wrapper@timerdef make_list():
    return [i * i for i in range(10**7)]my_list = make_list()# 程序用时: 0.8369960784912109s
ログイン後にコピー
3. 複数のデコレータを使用します module 新しく追加された

function 5

function 6 (番号順) を実行したい場合はどうすればよいでしょうか? 幸いなことに、Python では複数のデコレータを同時に使用できます:

def wrapper():
    import time
    tic = time.time()
    a = func()
    toc = time.time()
    print('程序用时: {}s'.format(toc - tic))
    return a
ログイン後にコピー
上記のプロセスは実際には次と同等です:
def func_5(original_module):
    def wrapper():
        original_module()
        print('功能5')
    return wrapperdef func_6(original_module):
    def wrapper():
        original_module()
        print('功能6')
    return wrapper@func_6@func_5def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5# 功能6
ログイン後にコピー
さらに、次のことに注意してください。複数のデコレータを使用する場合 デコレータを装飾する場合、

関数定義に最も近いデコレータが最初に関数を装飾します

装飾順序を変更すると、出力結果も変わります:

def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')new_module = func_6(func_5(module))new_module()
ログイン後にコピー
4 . パラメータ付きの装飾された関数

装飾された関数にパラメータがある場合、デコレーターを構築する方法は?

次のような関数を考えてみましょう:

def pide(a, b):
    return a / b
ログイン後にコピー

b=0 时会出现 ZeropisionError。如何在避免修改该函数的基础上给出一个更加人性化的提醒呢?

因为我们的 pide 函数接收两个参数,所以我们的 wrapper 函数也应当接收两个参数:

def smart_pide(func):
    def wrapper(a, b):
        if b == 0:
            return '被除数不能为0!'
        else:
            return func(a, b)
    return wrapper
ログイン後にコピー

使用该装饰器进行装饰:

@smart_pidedef pide(a, b):
    return a / bprint(pide(3, 0))# 被除数不能为0!print(pide(3, 1))# 3.0
ログイン後にコピー

如果不知道要被装饰的函数有多少个参数,我们可以使用下面更为通用的模板:

def decorator(func):
    def wrapper(*args, **kwargs):
        # ...
        res = func(*args, **kwargs)
        # ...
        return res  # 也可以不return
    return wrapper
ログイン後にコピー

五、带参数的装饰器

我们之前提到的装饰器都没有带参数,即语法糖 @decorator 中没有参数,那么该如何写一个带参数的装饰器呢?

前面实现的装饰器都是两层嵌套函数,而带参数的装饰器是一个三层嵌套函数。

考虑这样一个场景。假如我们在为 module 添加新功能时,希望能够加上实现该功能的开发人员的花名,则可以这样构造装饰器(以 功能5 为例):

def func_5_with_name(name=None):
    def func_5(original_module):
        def wrapper():
            original_module()
            print('功能5由{}实现'.format(name))
        return wrapper    return func_5
ログイン後にコピー

效果如下:

@func_5_with_name(name='若水')def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
ログイン後にコピー

对于这种三层嵌套函数,我们可以这样理解:当为 func_5_with_name 指定了参数后,func_5_with_name(name='若水') 实际上返回了一个 decorator,于是 @func_5_with_name(name='若水') 就相当于 @decorator

六、使用类作为装饰器

将类作为装饰器,我们需要实现 __init__ 方法和 __call__ 方法。

以计时器为例,具体实现如下:

class Timer:

    def __init__(self, func):
        self.func = func    def __call__(self):
        import time
        tic = time.time()
        self.func()
        toc = time.time()
        print('用时: {}s'.format(toc - tic))@Timerdef make_list():
    return [i**2 for i in range(10**7)]make_list()# 用时: 2.928966999053955s
ログイン後にコピー

如果想要自定义生成列表的长度并获得列表(即被装饰的函数带有参数情形),我们就需要在 __call__ 方法中传入相应的参数,具体如下:

class Timer:

    def __init__(self, func):
        self.func = func    def __call__(self, num):

        import time
        tic = time.time()
        res = self.func(num)
        toc = time.time()
        print('用时: {}s'.format(toc - tic))

        return res@Timerdef make_list(num):
    return [i**2 for i in range(num)]my_list = make_list(10**7)# 用时: 2.8219943046569824sprint(len(my_list))# 10000000
ログイン後にコピー

如果要构建带参数的类装饰器,则不能把 func 传入 __init__ 中,而是传入到 __call__ 中,同时 __init__ 用来初始化类装饰器的参数。

接下来我们使用类装饰器来复现第五章节中的效果:

class Func_5:

    def __init__(self, name=None):
        self.name = name    def __call__(self, func):

        def wrapper():
            func()
            print('功能5由{}实现'.format(self.name))

        return wrapper@Func_5('若水')def module():
    print('功能1')
    print('功能2')
    print('功能3')
    print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
ログイン後にコピー

七、内置装饰器

Python中有许多内置装饰器,这里仅介绍最常见的三种:@classmethod@staticmethod@property

7.1 @classmethod

@classmethod 用于装饰类中的函数,使用它装饰的函数不需要进行实例化也可调用。需要注意的是,被装饰的函数不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,它可以来调用类的属性,类的方法,实例化对象等。

cls 代表类本身,self 代表实例本身。

具体请看下例:

class A:

    num = 100

    def func1(self):
        print('功能1')

    @classmethod
    def func2(cls):
        print('功能2')
        print(cls.num)
        cls().func1()A.func2()# 功能2# 100# 功能1
ログイン後にコピー

7.2 @staticmethod

@staticmethod 同样用来修饰类中的方法,使用它装饰的函数的参数没有任何限制(即无需传入 self 参数),并且可以不用实例化调用该方法。当然,实例化后调用该方法也是允许的。

具体如下:

class A:

    @staticmethod
    def add(a, b):
        return a + bprint(A.add(2, 3))# 5print(A().add(2, 3))# 5
ログイン後にコピー

7.3 @property

使用 @property 装饰器,我们可以直接通过方法名来访问类方法,不需要在方法名后添加一对 () 小括号。

class A:

    @property
    def printer(self):
        print('Hello World')a = A()a.printer# Hello World
ログイン後にコピー

除此之外,@property 还可以用来防止类的属性被修改。考虑如下场景

class A:

    def __init__(self):
        self.name = 'ABC'a = A()print(a.name)# ABCa.name = 1print(a.name)# 1
ログイン後にコピー

可以看出类中的属性 name 可以被随意修改。如果要防止修改,则可以这样做

class A:

    def __init__(self):
        self.name_ = 'ABC'

    @property
    def name(self):
        return self.name_
    
    
a = A()print(a.name)# ABCa.name = 1print(a.name)# AttributeError: can't set attribute
ログイン後にコピー

推荐学习:python视频教程

以上がPythonのデコレーターの知識ポイントをまとめます。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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