Python學習之裝飾器與類的裝飾器詳解

通過學習裝飾器可以讓我們更好更靈活的使用函數,通過學會使用裝飾器還可以讓我們的代碼更加優雅。

在我們的實際工作中,很多場景都會用到裝飾器,比如記錄一些日志、或者屏蔽一些不太合法的程序執行從而使我們的代碼更加安全。

裝飾器

什麼是裝飾器?雖然對這個次感到陌生,但是完全不需要擔心。

首先,裝飾器也是一種函數;隻不過裝飾器可以接收 函數 作為參數來傳遞。

並且可以通過 return 可以返回一個函數,裝飾器通過接收一個函數,對它在裝飾器內部進行處理、調用,並返回一個新的函數,同時還可以動態增強傳入函數的功能。

裝飾器整個流程是這樣的:

  • A函數是裝飾器,B函數是A函數傳入的參數。
  • 將B函數在A函數中執行,在A函數中可以選擇執行或不執行,也可以對B函數的結果進行二次加工處理。

接下來我們看看 裝飾器長什麼樣子

def a():
    
    def b():
        print(helloworld)
        
    b()
    
    
a()
b()

a() 函數中書寫瞭一個 b() 函數,並在 a() 函數中調用 b() 函數。是不是非常類似在類中定義一個局部函數並調用的例子?其實裝飾器就是有些類似這樣的操作,隻不過被裝飾器調用的函數是通過 參數 的形式傳進去,並在 b() 函數中執行。

我們在定義完 a() 函數之後進行調用,可以正常處理。但是 b() 函數 是 a() 函數的局部函數 如果在外部調用會報錯。(如上文中的第十行,就會報錯)

裝飾器的定義

示例如下:

def out(func_args):            # 裝飾器的第一層函數被稱為 外圍函數 , 'func_args' 為要處理的函數
    
    def inter(*args, **kwargs):        # 外圍函數 的函數體內定義的函數被稱為 內嵌函數 ;傳入的參數為要處理的 func_args 函數的參數
                                    # 這裡我們並不知道 func_args 函數需要傳入進來的參數是什麼,所以目前寫傳入可變參數是比較合理的
        return func_args(*args, **kwargs)        # 在 內嵌函數 的函數體內調用 func_args 函數,並將可變參數傳入
                                                # 其實這裡我們可以處理更多的邏輯;
                                                # 我們可以選擇執行或者不執行,甚至可以func_args 函數的執行結果進行二次處理
    
    
    return inter             # 書寫完 內嵌函數的業務之後,我們在 外圍函數體內返回 內嵌函數
                            # 需要註意的是,這裡是不執行的(因為沒有加括號),這是裝飾器的定義規則,是必不可少的
                            # 隻有外圍函數返回內嵌函數,才可以被之後的代碼執行;(因為所有的業務都在內嵌函數中,不返回就無法執行調用)

裝飾器的用法

在我們日常工作中,裝飾器的使用方法有兩種。

第一種:將被調用的函數直接作用為參數傳入裝飾器的外圍函數括弧內;示例如下:

def a(func):
    
    def b(*args, **kwargs):
        return func(*args, **kwargs)
    
    return b


def c(name):
    print(name)
    

a(c)('Neo')

# >>> 執行結果如下:
# >>> Neo

第二種:將裝飾器與被調用函數綁定在一起, @ 符號 + 裝飾器函數放在被調用函數的上一行,被調用的函數正常定義,隻需要直接調用被執行函數即可。示例如下:

def a(func):
    
    def b(*args, **kwargs):
        return func(*args, **kwargs)
    
    return b


@a
def c(name):
    print(name)
    
    
c('Neo')

# >>> 執行結果如下:
# >>> Neo

最常用的裝飾器用法為第二種。

現在我們構建一個 檢查字符串類型的裝飾器,加深一下對裝飾器的理解。

def check_ok(func):

    def inner(*args, **kwargs):
        result = func(*args, **kwargs)
        if result == 'OK':
            return '傳入的參數數據為:\'%s\'' % result
        else:
            return '傳入的參數數據不為:\'OK\''

    return inner


@check_ok
def test_str(data):
    return data


result = test_str('OK')
print(result)

# >>> 執行結果如下:
# >>> 傳入的參數數據為:'OK'

result = test_str('NO')
print(result)

# >>> 執行結果如下:
# >>> 傳入的參數數據不為:'OK'

以上就是一個裝飾器的簡單用法,後續的學習內容會接觸到更多的高級用法。

類中的裝飾器

類的裝飾器 – classmethod

classmethod 的功能:可以將類函數不經過實例化即可直接被調用

classmethod 的用法:示例如下

@classmethod
def func(cls, ...):
    todo
    
# >>> cls 替代普通類函數中的 self ;
# >>> 變更為 cls ,表示當前的類
# >>> self 代表的是 實例化對象,所以隻有通過實例化後,才可以調用
# >>> cls 代表的是 類 ,所以即使不通過實例化,也可以調用 


# *****************************************************


class Test(object):

    @classmethod
    def add(cls, a, b):
        return a + b


print(Test.add(1, 3))

# >>> 執行結果如下:
# >>> 4

演示案例:

class Cat(object):
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(self.name, '喜歡吃魚')

    @classmethod
    def work(cls):
        print('會抓老鼠')


dragonLi = Cat('貍花貓')
print(dragonLi.eat(), dragonLi.work())

# >>> 執行結果如下:
# >>> 貍花貓 喜歡吃魚
# >>> 會抓老鼠

接下來我們不使用 類的實例化 ,直接使用 類 調用 eat() 函數 與 work() 函數

class Cat(object):
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(self.name, '喜歡吃魚')

    @classmethod
    def work(cls):
        print('會抓老鼠')


dragonLi = Cat('貍花貓')

Cat.eat()

# >>> 執行結果如下:
# >>> TypeError: Cat.eat() missing 1 required positional argument: 'self'
# >>> 報錯缺少重要參數 'self'  (沒有進行實例化的類,類無法直接調用類函數)

Cat.work()
# >>> 執行結果如下:
# >>> 會抓老鼠
# >>> 綁定瞭 classmethod 裝飾器 的 work() 函數,即使沒有實例化,也可以直接被 類 調用

再嘗試一下看看 沒有裝飾器的 eat() 函數 與 使用瞭 classmethod 裝飾器 work() 之間可不可以互相調用

class Cat(object):
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(self.name, '喜歡吃魚')

    @classmethod
    def work(cls):
        print('會抓老鼠')
        cls.eat()			# 在 classmethod 裝飾器的 work() 函數內 調用 eat() 函數


dragonLi = Cat('貍花貓')
dragonLi.work()

# >>> 執行結果如下:
# >>> TypeError: Cat.eat() missing 1 required positional argument: 'self'
# >>> 同樣報錯缺少重要參數 'self' 
class Cat(object):
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(self.name, '喜歡吃魚')
        self.work()

    @classmethod
    def work(cls):
        print('會抓老鼠')


dragonLi01 = Cat('貍花貓')
dragonLi01.eat()

# >>> 執行結果如下:
# >>> 執行結果如下:
# >>> 貍花貓 喜歡吃魚
# >>> 會抓老鼠

綜合以上兩個場景,我們得出以下結論:

  • 在帶有 classmethod 裝飾器 的 函數 內,是無法調用普通的 帶有 self 的函數的
  • 但是在普通的帶有 self 的類函數內,是可以調用帶有 classmethod 裝飾器 的 函數的

類的裝飾器 – staticmethod

staticmethod 的功能:可以將 類函數 不經過實例化而直接被調用,被該裝飾器調用的函數不需要傳入 self 、cls 參數,並且無法在該函數內調用其他 類函數 或 類變量

staticmethod 的用法:參考如下

@staticmethod
def func(...):
    todo
    
# >>> 函數內無需傳入 cls 或 self 參數


# *****************************************************


class Test(object):
    
    @staticmethod
    def add(a, b):
        return a + b


print(Test.add(1, 3))

# >>> 執行結果如下:
# >>> 4

接下來我們在上文的 Cat() 類基礎演示一下 staticmethod 裝飾器 (新建一個 color() 函數,使用 staticmethod 裝飾器 )

class Cat(object):
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(self.name, '喜歡吃魚')
        self.work()

    @classmethod
    def work(cls):
        print('會抓老鼠')

    @staticmethod
    def color():
        print('黃棕色')


dragonLi = Cat('貍花貓')
print(dragonLi.eat(), dragonLi.color())

# >>> 執行結果如下:
# >>> 貍花貓 喜歡吃魚
# >>> 會抓老鼠
# >>> 黃棕色
# >>> 從執行結果來看, staticmethod 裝飾器的 color() 函數可以被實例化後的對象 dragonLi 調用。
# >>> 那麼可以被 Cat() 類 直接調用麼?我們往下看


print(Cat.color())

# >>> 執行結果如下:
# >>> 黃棕色
# >>> 可以看到,staticmethod 裝飾器構造的 color() 函數,即使沒有被實例化,依然可以直接被 類 調用

同樣的,也嘗試一下 staticmethod 裝飾器構造的 color() 函數 是否能夠在類函數中互相調用。

class Cat(object):
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(self.name, '喜歡吃魚')
        self.work()
        self.color()

    @classmethod
    def work(cls):
        print('會抓老鼠')

    @staticmethod
    def color():
        print('黃棕色')


dragonLi = Cat('貍花貓')
dragonLi.eat()

# >>> 執行結果如下:
# >>> 貍花貓 喜歡吃魚
# >>> 會抓老鼠
# >>> 黃棕色				
# >>> 結合執行結果得出結論:staticmethod 裝飾器構造的 color() 函數 可以在 eat() 類函數中被調用

與帶有 classmethod 裝飾器 的 函數 一樣,staticmethod 裝飾器構造的 函數也是無法調用普通的 帶有 self 的函數的,這裡就不再書寫演示代碼進行演示瞭。(staticmethod 裝飾器構造的 函數也是無法調用普通的 帶有 self 的函數會報錯 : NameError: name 'self' is not defined )

類的裝飾器 – property

property 的功能:可以將類函數的執行免去小括號,類似於直接調用類的變量(屬性)

staticmethod 的用法:參考如下

@property
def func(self):
    todo
    
# >>> 不能傳入參數,無重要函數說明


# *************************示例如下*************************


class Test(object):

    def __init__(self, name):
        self.name = name

    @property
    def call_name(self):
        return 'Hello {}'.format(self.name)


test = Test('Neo')
result = test.call_name			# 不需要添加 小括號 即可調用 call_name 函數;
								# 關於 staticmethod 不可傳入參數,其實並不是不可以傳入參數,而是傳入的方法比較另類,我們繼續往下看
print(result)

# >>> 執行結果如下:
# >>> Hello Neo

重新創建一個 Dog 類 ,然後我們繼續演示。

class Dog(object):

    def __init__(self, name):
        self.__name = name

    @property
    def type(self):
        if self.__name in ['哈士奇', '薩摩耶', '阿拉斯基']:
            return self.__name, '是雪橇犬,\'雪橇三傻\'之一'
        elif self.__name in ['吉娃娃', '博美犬', '約克夏']:
            return self.__name, '是小型犬'
        else:
            return self.__name, '我暫時不知道這是什麼犬種,也許它是\'泰日天\'的親戚'


dog = Dog(name='哈士奇')
print(dog.type)			# 這裡我們並不需要 dog.type + () 小括號,即可調用 type() 函數

# >>> 執行結果如下:
# >>> ('哈士奇', "是雪橇犬,'雪橇三傻'之一")
# >>> 這裡我們看到 當 Dog 類 實例化 dog 變量之後,我們傳入的 '哈士奇' 參數是不可更改的,如果我們嘗試利用賦值的方式修改傳入的參數呢?

dog = Dog(name='哈士奇')
dog.type = '約克夏'
print(dog.type)

# >>> 執行結果如下:
# >>> AttributeError: can't set attribute
# >>> 報錯:屬性錯誤,不可以設置這個屬性
# >>> 其實,property 裝飾器綁定的函數的參數並不是不可以更改,隻是更改的方式比較特殊,並不是不能通過賦值的形式傳入參數,我們繼續往下看。

首先,我們已經使用瞭 @property 綁定瞭我們的 type 函數,這是一個返回值的方法。 所以我們要如何給 type() 函數賦值呢?其實很簡單,我們可以通過 @type 對應上 type() 函數,在它的函數內部有一個函數 setter ;然後再定義一個 type 函數,在這個新定義的 type() 函數內定義一個值 value (可以是任意的名字,但這裡需要註意,隻能定義一個值)。然後再通過設置一個 self.__name = value ,如此就可以達到修改傳入參數的目的。廢話不多說瞭,看下方的示例:

class Dog(object):

    def __init__(self, name):
        self.__name = name

    @property
    def type(self):
        if self.__name in ['哈士奇', '薩摩耶', '阿拉斯基']:
            return self.__name, '是雪橇犬,\'雪橇三傻\'之一'
        elif self.__name in ['吉娃娃', '博美犬', '約克夏']:
            return self.__name, '是小型犬'
        else:
            return self.__name, '我暫時不知道這是什麼犬種,也許它是\'泰日天\'的親戚'

    @type.setter
    def type(self, value):
        self.__name = value


dog = Dog(name='哈士奇')
dog.type = '約克夏'
print(dog.type)

# >>> 執行結果如下:
# >>> ('約克夏', '是小型犬')

附:使用最廣泛的裝飾器為 classmethod

以上就是Python學習之裝飾器與類的裝飾器詳解的詳細內容,更多關於Python裝飾器的資料請關註WalkonNet其它相關文章!

推薦閱讀: