亚洲最大看欧美片,亚洲图揄拍自拍另类图片,欧美精品v国产精品v呦,日本在线精品视频免费

  • 站長(zhǎng)資訊網(wǎng)
    最全最豐富的資訊網(wǎng)站

    歸納總結(jié)Python中的裝飾器知識(shí)點(diǎn)

    本篇文章給大家?guī)?lái)了關(guān)于python的相關(guān)知識(shí),其中主要介紹了關(guān)于裝飾器的相關(guān)問(wèn)題,包括了閉包、裝飾器、使用多個(gè)裝飾器、帶參數(shù)的裝飾器等等內(nèi)容,下面一起來(lái)看一下,希望對(duì)大家有幫助。

    歸納總結(jié)Python中的裝飾器知識(shí)點(diǎn)

    推薦學(xué)習(xí):python視頻教程

    一、閉包

    要了解什么是裝飾器(decorator),我們首先需要知道閉包(closure)的概念。

    閉包,又稱閉包函數(shù)或者閉合函數(shù),通俗一點(diǎn)來(lái)講,當(dāng)某個(gè)函數(shù)被當(dāng)成對(duì)象返回時(shí)還夾帶了外部變量,就形成了一個(gè)閉包。

    以打印Hello World為例,我們先來(lái)看一下嵌套函數(shù)的結(jié)構(gòu)應(yīng)該是什么樣的:

    def print_msg(msg):      def printer():         print(msg)      printer()print_msg('Hello World')# Hello World

    執(zhí)行 print_msg('Hello World') 相當(dāng)于執(zhí)行了 printer(),也就是執(zhí)行 print(msg),所以將輸出 Hello World

    我們?cè)賮?lái)看一下如果是閉包,該是什么樣的結(jié)構(gòu):

    def print_msg(msg):      def printer():         print(msg)      return printer   my_msg = print_msg('Hello World')my_msg()# Hello World

    本例中的 printer 函數(shù)就是閉包。

    執(zhí)行 print_msg('Hello World') 實(shí)際上是返回了如下這樣一個(gè)函數(shù),它夾帶了外部變量 'Hello World'

    def printer():     print('Hello World')

    于是調(diào)用 my_msg 就相當(dāng)于執(zhí)行 printer()。


    那么如何判斷一個(gè)函數(shù)是否是閉包函數(shù)呢?閉包函數(shù)的 __closure__ 屬性里面定義了一個(gè)元組用于存放所有的cell對(duì)象,每個(gè)cell對(duì)象保存了這個(gè)閉包中所有的外部變量。而普通函數(shù)的 __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>,)

    由此可見(jiàn) outer 函數(shù)不是閉包,而 inner 函數(shù)是閉包。

    我們還可以查看閉包所攜帶的外部變量:

    print(inner.__closure__[0].cell_contents)# Hello World

    說(shuō)了那么多,那么閉包究竟有什么用呢?閉包存在的意義就是它夾帶了外部變量(私貨),如果它不夾帶私貨,那么就和普通的函數(shù)沒(méi)有任何區(qū)別。

    閉包的優(yōu)點(diǎn)如下:

    • 局部變量無(wú)法共享和長(zhǎng)久的保存,而全局變量可能造成變量污染,閉包既可以長(zhǎng)久的保存變量又不會(huì)造成全局污染。
    • 閉包使得函數(shù)內(nèi)局部變量的值始終保持在內(nèi)存中,不會(huì)在外部函數(shù)調(diào)用后被自動(dòng)清除。

    二、裝飾器

    我們先考慮這樣一個(gè)場(chǎng)景,假設(shè)先前編寫的一個(gè)函數(shù)已經(jīng)實(shí)現(xiàn)了4個(gè)功能,為簡(jiǎn)便起見(jiàn),我們用 print 語(yǔ)句來(lái)代表每一個(gè)具體的功能:

    def module():     print('功能1')     print('功能2')     print('功能3')     print('功能4')

    現(xiàn)在,由于某種原因,你需要為 module 這個(gè)函數(shù)新增一個(gè) 功能5,你完全可以這樣修改:

    def module():     print('功能1')     print('功能2')     print('功能3')     print('功能4')     print('功能5')

    但在現(xiàn)實(shí)業(yè)務(wù)中,直接做出這樣的修改往往是比較危險(xiǎn)的(會(huì)變得不易于維護(hù))。那么如何在不修改原函數(shù)的基礎(chǔ)上去為它新添一個(gè)功能呢?

    你可能已經(jīng)想到了使用之前的閉包知識(shí):

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

    func_5 代表該函數(shù)主要用于實(shí)現(xiàn) 功能5,我們接下來(lái)將 module 傳入進(jìn)去來(lái)觀察效果:

    new_module = func_5(module)new_module()# 功能1# 功能2# 功能3# 功能4# 功能5

    可以看出,我們的新模塊:new_module 已經(jīng)實(shí)現(xiàn)了 功能5。

    在上面的例子中,函數(shù) func_5 就是一個(gè)裝飾器,它裝飾了原來(lái)的模塊(為它新添了一個(gè)功能)。

    當(dāng)然,Python有更簡(jiǎn)潔的寫法(稱之為語(yǔ)法糖),我們可以將@符號(hào)與裝飾器函數(shù)的名稱一起使用,并將其放置在要裝飾的函數(shù)的定義上方:

    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

    基于此,我們可以在不修改原函數(shù)的基礎(chǔ)上完成計(jì)時(shí)任務(wù)(計(jì)算原函數(shù)的運(yùn)行時(shí)間),如下:

    def timer(func):      def wrapper():         import time         tic = time.time()         func()         toc = time.time()         print('程序用時(shí): {}s'.format(toc - tic))      return wrapper@timerdef make_list():     return [i * i for i in range(10**7)]my_list = make_list()# 程序用時(shí): 0.8369960784912109s

    事實(shí)上,my_list 并不是列表,直接打印會(huì)顯示 None,這是因?yàn)槲覀兊?wrapper 函數(shù)沒(méi)有設(shè)置返回值。如果需要獲得 make_list 的返回值,可以這樣修改 wrapper 函數(shù):

    def wrapper():     import time     tic = time.time()     a = func()     toc = time.time()     print('程序用時(shí): {}s'.format(toc - tic))     return a

    三、使用多個(gè)裝飾器

    假如我們要為 module 新添 功能5功能6(按數(shù)字順序),那該如何做呢?

    好在Python允許同時(shí)使用多個(gè)裝飾器:

    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

    上述過(guò)程實(shí)際上等價(jià)于:

    def module():     print('功能1')     print('功能2')     print('功能3')     print('功能4')new_module = func_6(func_5(module))new_module()

    此外,需要注意的是,在使用多個(gè)裝飾器時(shí),最靠近函數(shù)定義的裝飾器會(huì)最先裝飾該函數(shù),如果我們改變裝飾順序,則輸出結(jié)果也將改變:

    @func_5@func_6def module():     print('功能1')     print('功能2')     print('功能3')     print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能6# 功能5

    四、被裝飾的函數(shù)帶有參數(shù)

    如果被裝飾的函數(shù)帶有參數(shù),那該如何去構(gòu)造裝飾器呢?

    考慮這樣一個(gè)函數(shù):

    def pide(a, b):     return a / b

    當(dāng)b=0 時(shí)會(huì)出現(xiàn) ZeropisionError。如何在避免修改該函數(shù)的基礎(chǔ)上給出一個(gè)更加人性化的提醒呢?

    因?yàn)槲覀兊?pide 函數(shù)接收兩個(gè)參數(shù),所以我們的 wrapper 函數(shù)也應(yīng)當(dāng)接收兩個(gè)參數(shù):

    def smart_pide(func):     def wrapper(a, b):         if b == 0:             return '被除數(shù)不能為0!'         else:             return func(a, b)     return wrapper

    使用該裝飾器進(jìn)行裝飾:

    @smart_pidedef pide(a, b):     return a / bprint(pide(3, 0))# 被除數(shù)不能為0!print(pide(3, 1))# 3.0

    如果不知道要被裝飾的函數(shù)有多少個(gè)參數(shù),我們可以使用下面更為通用的模板:

    def decorator(func):     def wrapper(*args, **kwargs):         # ...         res = func(*args, **kwargs)         # ...         return res  # 也可以不return     return wrapper

    五、帶參數(shù)的裝飾器

    我們之前提到的裝飾器都沒(méi)有帶參數(shù),即語(yǔ)法糖 @decorator 中沒(méi)有參數(shù),那么該如何寫一個(gè)帶參數(shù)的裝飾器呢?

    前面實(shí)現(xiàn)的裝飾器都是兩層嵌套函數(shù),而帶參數(shù)的裝飾器是一個(gè)三層嵌套函數(shù)。

    考慮這樣一個(gè)場(chǎng)景。假如我們?cè)跒?module 添加新功能時(shí),希望能夠加上實(shí)現(xiàn)該功能的開(kāi)發(fā)人員的花名,則可以這樣構(gòu)造裝飾器(以 功能5 為例):

    def func_5_with_name(name=None):     def func_5(original_module):         def wrapper():             original_module()             print('功能5由{}實(shí)現(xiàn)'.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由若水實(shí)現(xiàn)

    對(duì)于這種三層嵌套函數(shù),我們可以這樣理解:當(dāng)為 func_5_with_name 指定了參數(shù)后,func_5_with_name(name='若水') 實(shí)際上返回了一個(gè) decorator,于是 @func_5_with_name(name='若水') 就相當(dāng)于 @decorator。

    六、使用類作為裝飾器

    將類作為裝飾器,我們需要實(shí)現(xiàn) __init__ 方法和 __call__ 方法。

    以計(jì)時(shí)器為例,具體實(shí)現(xiàn)如下:

    class Timer:      def __init__(self, func):         self.func = func    def __call__(self):         import time         tic = time.time()         self.func()         toc = time.time()         print('用時(shí): {}s'.format(toc - tic))@Timerdef make_list():     return [i**2 for i in range(10**7)]make_list()# 用時(shí): 2.928966999053955s

    如果想要自定義生成列表的長(zhǎng)度并獲得列表(即被裝飾的函數(shù)帶有參數(shù)情形),我們就需要在 __call__ 方法中傳入相應(yīng)的參數(shù),具體如下:

    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('用時(shí): {}s'.format(toc - tic))          return res@Timerdef make_list(num):     return [i**2 for i in range(num)]my_list = make_list(10**7)# 用時(shí): 2.8219943046569824sprint(len(my_list))# 10000000

    如果要構(gòu)建帶參數(shù)的類裝飾器,則不能把 func 傳入 __init__ 中,而是傳入到 __call__ 中,同時(shí) __init__ 用來(lái)初始化類裝飾器的參數(shù)。

    接下來(lái)我們使用類裝飾器來(lái)復(fù)現(xiàn)第五章節(jié)中的效果:

    class Func_5:      def __init__(self, name=None):         self.name = name    def __call__(self, func):          def wrapper():             func()             print('功能5由{}實(shí)現(xiàn)'.format(self.name))          return wrapper@Func_5('若水')def module():     print('功能1')     print('功能2')     print('功能3')     print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水實(shí)現(xiàn)

    七、內(nèi)置裝飾器

    Python中有許多內(nèi)置裝飾器,這里僅介紹最常見(jiàn)的三種:@classmethod、@staticmethod@property。

    7.1 @classmethod

    @classmethod 用于裝飾類中的函數(shù),使用它裝飾的函數(shù)不需要進(jìn)行實(shí)例化也可調(diào)用。需要注意的是,被裝飾的函數(shù)不需要 self 參數(shù),但第一個(gè)參數(shù)需要是表示自身類的 cls 參數(shù),它可以來(lái)調(diào)用類的屬性,類的方法,實(shí)例化對(duì)象等。

    cls 代表類本身,self 代表實(shí)例本身。

    具體請(qǐng)看下例:

    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 同樣用來(lái)修飾類中的方法,使用它裝飾的函數(shù)的參數(shù)沒(méi)有任何限制(即無(wú)需傳入 self 參數(shù)),并且可以不用實(shí)例化調(diào)用該方法。當(dāng)然,實(shí)例化后調(diào)用該方法也是允許的。

    具體如下:

    class A:      @staticmethod     def add(a, b):         return a + bprint(A.add(2, 3))# 5print(A().add(2, 3))# 5

    7.3 @property

    使用 @property 裝飾器,我們可以直接通過(guò)方法名來(lái)訪問(wèn)類方法,不需要在方法名后添加一對(duì) () 小括號(hào)。

    class A:      @property     def printer(self):         print('Hello World')a = A()a.printer# Hello World

    除此之外,@property 還可以用來(lái)防止類的屬性被修改??紤]如下場(chǎng)景

    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

    推薦學(xué)習(xí):python視頻教程

    贊(0)
    分享到: 更多 (0)
    網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)