iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 11
2

裝飾器進階課程

在上一章的課程介紹了裝飾器的基本語法與應用,還有介紹非特定參數的傳入方式以及@語法糖的概念,接下來要介紹一些進階的內容。

本文章同步放置於此

靜態方法、類別方法

在介紹完@語法糖以及類別後就可以進一步介紹@staticmethod@classmethod,其實看到@讀者們就知道看到裝飾器了,至於這兩個裝飾器有何作用呢?

@staticmethod

這是靜態函式的裝飾器,當有些類別的方法想要直接使用時會遇到一個窘境,還記得類別方法是宣告的嗎,看看下列式子吧:

class ObjectA:
    def func(self, *args, **kwargs):
        print('do some thing')

好了問題來了,大家還記得類別方法的第一個參數是甚麼嗎?那就是類別的實體本身,因此要使用此類別的方法實際就要依照下列式子的方式來使用:

var = ObjectA()
var.func()

不過讀者會問說,我僅是要使用該方法,為什麼要產生該物件的實體呢?這時候@staticmethod就需要出馬了,最上面類別的宣告要改成以下方式:

class ObjectA:
    @staticmethod
    def func(*args, **kwargs):
        print('do some thing')

如此要使用該靜態方法就不需要產生類別的實體了。

@classmethod

相較於靜態方方法,還有另外一種裝飾器就是類別方法@classmethod,相對於靜態方法的裝飾器可以使類別方法不需要綁定在物件實體本身上,類別方法裝飾器則是將類別方法強制給予一個類別實體,請各位先看看以下宣告的方式:

class ObjectB:
    @classmethod
    def func(cls, *args, **kwargs):
        print('do some thing')

雖然觀念不太一樣,但是類別方法的使用方式與靜態方法一樣,就是透過類別名稱.類別方法的方式直接使用,無須產生類別實體。

兩者差異

同時講了靜態方法與類別方法,讀者應該會搞混,這兩個裝飾器用下去該方法都不需要綁定物件的實體,那這兩個裝飾器差別在哪裡,甚麼時候要用哪一個呢?這時候把兩個宣告方式擺再一起看一看:

# 靜態方法
    @staticmethod
    def func(*args, **kwargs):
# 類別方法
    @classmethod
    def func(cls, *args, **kwargs):

其實答案就在方法的宣告上,靜態方法裝飾器上沒有給予類別的實體,而類別方法裝飾器第一個參數就是綁訂在一個類別物件的實體cls,這時候不管產生多少類別物件的實體,他所用到的類別方法所綁定的類別物件實體都會是同一個,所以說需要用到一個共用的類別物件的實體時就需要使用類別方法。

類別裝飾器

裝飾器不僅可以以方法的方式來創建,亦可以以類別的方式來創建,請大家看看下列例子:

import types


class ClassDecorator:
    def __init__(self, func):
        self.numberOfCalls = 0
        self.func = func

    def __call__(self, *args, **kwargs):
        self.numberOfCalls += 1
        return self.func(*args, **kwargs)

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

以上例子就是如何創建一個列別裝飾器,眼尖的讀者看到兩個新的關鍵字__call____get__,在解釋這兩個關鍵字之前先看看類別裝飾器如何使用:

@ClassDecorator
def add(x, y):
    return x + y


class Decorated:
    @ClassDecorator
    def bar(self, x):
        print(self, x)


print(add(2, 3))
print(add(4, 5))
print(add.numberOfCalls)
s = Decorated()
s.bar(1)
s.bar(2)
s.bar(3)
print(Decorated.bar.numberOfCalls)

由例子可以看到__call__是定義當該方法被呼叫到時的處置方式,那許多讀者就會問說__get__是做甚麼用的呢,他是有必要存在的嗎。這時讀者們可以自行把__get__去除掉是是看,相信沒了這東西應該會看調下列錯誤訊息:

TypeError: bar() missing 1 required positional argument: 'x'

由此可知__get__方法是为了確保綁定方法對象能被正確的創建。不過若是@ClassDecorator僅使用於方法上則__get__可有可無,不過若是要使用在類別的方法上,則__get__就一定需要實作了。關於這部分的知識有興趣的讀者可以參考python3-cookbook->9.9 将装饰器定义为类

多裝飾幾層

在理解@語法糖後相信下列式子應該不難理解:

@a
@b
@c
def d():
    pass

不知道所有的讀者是否有答案了呢,如果沒有答案的話可以看看下列更進一步的解釋:

d = a(b(c(d)))

其實最前面的式子與上述式子式等價的,所以由此可知多裝幾層裝飾器它是由上而下的包裝起來的,所以執行時會由上而下的執行完裝飾器後再執行方法本身。

小結

好了,裝飾器的進階內容說明完了,接下來要介紹如何使用別人寫的函式庫以及如何自己撰寫函式庫,敬請期待。


上一篇
DAY09-搞懂Python的裝飾器
下一篇
DAY11-搞懂Python中如何使用以及建立自己的函式庫
系列文
瓶子裡裝甚麼藥,使用Flask輕輕鬆鬆打造一個RESTful API31

尚未有邦友留言

立即登入留言