亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Python中的各種裝飾器解析

 更新時(shí)間:2023年11月03日 09:55:55   作者:驚瑟  
這篇文章主要介紹了Python中的各種裝飾器解析,Python裝飾器可以在不改變函數(shù)原實(shí)現(xiàn)方式的前提下,為函數(shù)添加額外的功能,需要的朋友可以參考下

前言

Python裝飾器可以在不改變函數(shù)原實(shí)現(xiàn)方式的前提下,為函數(shù)添加額外的功能。裝飾器的功能并不費(fèi)解(func = decorator(func) ),但具體實(shí)現(xiàn)時(shí)有一些細(xì)節(jié)還是需要搞明白。

一、函數(shù)裝飾器

為表述方便,下面例子decorator開頭的表示裝飾器函數(shù),func表示被裝飾函數(shù)。

decorator不帶參數(shù)

#不帶參數(shù)的裝飾器

def decorator_without_args(func):
    def wrapper(msg):
        print('I got a decorator')
        return func(msg)
    return wrapper

@decorator_without_args
def func(msg):
    print(f'msg is : {msg}')
    
func('hello world')

輸出如下:

I got a decorator
msg is : hello world

上面就是最簡單的裝飾器,值得注意的是:

  • 不帶參的decorator裝飾函數(shù)時(shí),勿帶括號@decorator_without_args
  • 裝飾器外層函數(shù)的return不加括號,如return wrapper
  • func本身傳給外層的函數(shù)decorator_without_args(),而func的參數(shù)msg則傳給內(nèi)層函數(shù)wrapper()
  • func本身def func(msg),與def wrapper(msg)以及wrapper中return func(msg)的參數(shù)個數(shù)必須保持一致,不一致就會報(bào)錯。這個特性可以理解為裝飾器的內(nèi)層函數(shù)(wrapper)有兩個使命:首先是添加額外的功能,其次是將被裝飾函數(shù)(func)的參數(shù)msg接收過來,以便最后rerun func(msg)。而第二點(diǎn)其實(shí)和裝飾器本身想實(shí)現(xiàn)的功能是無關(guān)的,所以為了有更好的通用性,我們可以將裝飾器改造為以下形式,這也是日常所見到的最普遍的形式:
def decorator_without_args(func):
    def wrapper(*args, **kwargs):
        print('I got a decorator')
        return func(*args, **kwargs)
    return wrapper

decorator帶參數(shù)

實(shí)際項(xiàng)目中,除了func本身需要傳參外,有時(shí)候decorator也需要傳入?yún)?shù),此時(shí)寫法略有不同:

#帶參數(shù)的裝飾器

def decorator_with_args(flag):
    def decorator(func):
        def wrapper(*agrs, **kwargs):
            if flag:
                print('flag is True')
            else:
                print('flag is False')
            return func(*agrs, **kwargs)
        return wrapper
    return decorator

@decorator_with_args(True)
def func(msg):
    print(f'msg is : {msg}')

func('hello world')

值得注意的是:

  • 以上示例相當(dāng)于func = decorator_with_args(flag)(func)
  • 帶參的decorator裝飾函數(shù)時(shí),請加上括號并傳入需要的參數(shù) @decorator_with_args(True)
  • 一共有三層,最外層用于接收裝飾器函數(shù)的參數(shù),內(nèi)層和不帶參的裝飾器一樣。因此可以得出結(jié)論:若裝飾器函數(shù)帶參,需要寫成三層,最外層用于接收裝飾器函數(shù)的參數(shù),中間一層接受被裝飾函數(shù)func,最里面一層用于接收被裝飾函數(shù)func的參數(shù)(*args, **kwargs)

二、類的裝飾器

表示在類上使用的裝飾器,類是被裝飾對象,請區(qū)分“類裝飾器”,舉個最經(jīng)典的例子—裝飾器實(shí)現(xiàn)單例模式:

# 類的裝飾器

def decorator_singleton(cls):
    def wrapper(*args, **kwargs):
        if not hasattr(cls, '_instance'):
            print('I am created firstly.')
            cls._instance = cls(*args, **kwargs)
        else:
            print('I have been created,so I am just an old instance!!!')
        return cls._instance
    return wrapper

@decorator_singleton
class Register:
    pass

a = Register()
b = Register()
id_a = id(a)
id_b = id(b)

print(f'a id is:{id_a} \nb id is:{id_b}')

輸出如下:

I am created firstly.
I have been created,so I am just an old instance!!!
a id is:140077188368368 
b id is:140077188368368

可以看出來,單例裝飾器起到了作用,執(zhí)行過程可以這么表示:

Register = decorator_singleton(Register)

相當(dāng)于將類本身作為參數(shù)傳給裝飾器,最后裝飾器再返回一個實(shí)例對象。事實(shí)上,把Register視作一個函數(shù)就很好理解了,畢竟Register實(shí)例化時(shí),也要執(zhí)行Register()。

三、類裝飾器

表示類作為裝飾器,此時(shí)裝飾器是用類實(shí)現(xiàn)的,與“類的裝飾器”做區(qū)分。

# 類裝飾器

class Decorator:

    def __init__(self, func):
        print('I am initialing')
        self.func = func

    def __call__(self, *args, **kwargs):
        print('I am called')
        return self.func(*args, **kwargs)
    
@Decorator
def func(msg):
    print(f'msg is {msg}')

func('hello world')

輸出:

I am initialing
I am called
msg is hello world

如果明白了裝飾器的原理,類裝飾器也很好理解。事實(shí)上,任何類型裝飾器,要起到作用,有兩個隱式前提:

  • 裝飾器本身是一個可調(diào)用對象(callable),畢竟本質(zhì)上繞不開func = decorator(func)
  • 必須向裝飾器傳入兩類參數(shù):一是func本身,而是func的參數(shù)(*args, **kwargs)

在Python中,對于類對象class,只要實(shí)現(xiàn)了__call__()方法,該類實(shí)例化后就可以像調(diào)用函數(shù)一樣使用這個實(shí)例。上面示例中,類Decorator實(shí)例化后可以像函數(shù)一樣被調(diào)用,這就滿足了裝飾器第一個前提。 對于第二個前提,眾所周知,類的初始化變量可以通過__init__()接收,因此,通過init將func傳入類中,而func中參數(shù)則被傳到了__call__()中。類的實(shí)例化過程想必大家也都知道,總是先執(zhí)行init(),因此I am initialing最先被打印出來。此處小伙伴可能有疑問了,可不可以將func和func中參數(shù)全都傳給__call__(),從而省去__init__()呢?或者說,可不可以將func和func中的參數(shù)全都傳給__init()__而省去__call__()呢?答案是不可以。事實(shí)上,裝飾的過程分為兩步:

func = decorator(func)
func(args)

兩類參數(shù)(被裝飾函數(shù)本身,及其參數(shù))也是分兩次傳入裝飾器的。對于類裝飾器,第一步其實(shí)是實(shí)例化類裝飾器,所以func只能傳給__init__()。而func的args則必須傳給__call__(),至于為什么,請看下面結(jié)論。此處有沒有似曾相識的感覺?其實(shí)跟函數(shù)裝飾器func要傳給外層函數(shù),args傳給內(nèi)層函數(shù)一個道理。

三、結(jié)論

堅(jiān)持看完的小伙伴下次一定對裝飾器的原理了然于胸了。而且可能還有額外的收獲,比如類裝飾器的原理其實(shí)跟類實(shí)現(xiàn)了__call__()就可以像函數(shù)那樣調(diào)用的原理一樣,來看例子:

class CallTest:
    def __init__(self):
        pass
    def __call__(self, msg):
        print(msg)

ct = CallTest()
ct('I am called')

輸出:

I am called

這個例子可以明顯看出來,類在實(shí)例化出對象后,對象的參數(shù)其實(shí)就是傳給了__call__()了。一言以蔽之,Python的()運(yùn)算符其實(shí)就是__call__(),再來看上面類裝飾器裝飾的過程:

func = decorator(func)
func(args)

第二步func(args)本質(zhì)上就是func.__call__(agrs),所以agrs只能傳給__call__()。

到此這篇關(guān)于Python中的各種裝飾器解析的文章就介紹到這了,更多相關(guān)Python裝飾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論