【集齊閉包裝飾器7大必知點】【Python高級】裝飾器來龍去脈,看這篇就夠了

  1. Python高級

1、閉包

# 定義一個外部函數
def func_out(num1):
    # 定義一個內部函數
    def func_inner(num2):
        # 內部函數使用了外部函數的變量(num1)
        result = num1 + num2
        print("結果是:", result)
    # 外部函數返回了內部函數,這裏返回的內部函數就是閉包
    return func_inner

# 創建閉包實例    
f = func_out(1)
# 執行閉包
f(2)
f(3)

運行結果:

結果是: 3
結果是: 4

閉包的作用

  • 閉包可以保存外部函數內的變量,不會隨着外部函數調用完而銷燬。

注意點:

  • 由於閉包引用了外部函數的變量,則外部函數的變量沒有及時釋放,消耗內存。

案例

需求: 根據配置信息使用閉包實現不同人的對話信息,例如對話:

Tom: 到北京了嗎?
Jerry: 已經到了,放心吧。

# 外部函數
def config_name(name):
    # 內部函數
    def say_info(info):
        print(name + ": " + info)

    return say_info

tom = config_name("Tom")

tom("到北京了嗎")

jerry = config_name("已經到了")

外部變量

# 定義一個外部函數
def func_out(num1):

    # 定義一個內部函數
    def func_inner(num2):
        # 這裏本意想要修改外部num1的值,實際上是在內部函數定義了一個局部變量num1
        nonlocal num1  # 告訴解釋器,此處使用的是 外部變量a
        # 修改外部變量num1
        num1 = 10
        # 內部函數使用了外部函數的變量(num1)
        result = num1 + num2
        print("結果是:", result)

    print(num1)
    func_inner(1)
    print(num1)

    # 外部函數返回了內部函數,這裏返回的內部函數就是閉包
    return func_inner

# 創建閉包實例
f = func_out(1)
# 執行閉包
f(2)

小結

  • 修改閉包內使用的外部函數變量使用 nonlocal 關鍵字來完成。

2、裝飾器

1 定義

就是給已有函數增加額外功能的函數,它本質上就是一個閉包函數

裝飾器的功能特點:

  1. 不修改已有函數的源代碼
  2. 不修改已有函數的調用方式
  3. 給已有函數增加額外的功能

2. 裝飾器的示例代碼

# 添加一個登錄驗證的功能
def check(fn):
    def inner():
        print("請先登錄....")
        fn()
    return inner


def comment():
    print("發表評論")

# 使用裝飾器來裝飾函數
comment = check(comment)
comment()

# 裝飾器的基本雛形
# def decorator(fn): # fn:目標函數.
#     def inner():
#         '''執行函數之前'''
#         fn() # 執行被裝飾的函數
#         '''執行函數之後'''
#     return inner

代碼說明:

  • 閉包函數有且只有一個參數,必須是函數類型,這樣定義的函數纔是裝飾器。
  • 寫代碼要遵循開放封閉原則,它規定已經實現的功能代碼不允許被修改,但可以被擴展。

執行結果:

請先登錄....
發表評論

3. 裝飾器的語法糖寫法

如果有多個函數都需要添加登錄驗證的功能,每次都需要編寫func = check(func)這樣代碼對已有函數進行裝飾,這種做法還是比較麻煩。

Python給提供了一個裝飾函數更加簡單的寫法,那就是語法糖,語法糖的書寫格式是: @裝飾器名字,通過語法糖的方式也可以完成對已有函數的裝飾

# 添加一個登錄驗證的功能
def check(fn):
    print("裝飾器函數執行了")
    def inner():
        print("請先登錄....")
        fn()
    return inner

# 使用語法糖方式來裝飾函數
@check
def comment():
    print("發表評論")


comment()

說明:

  • @check 等價於 comment = check(comment)
  • 裝飾器的執行時間是加載模塊時立即執行。

執行結果:

請先登錄....
發表評論

4. 小結

  • 裝飾器本質上就是一個閉包函數,它可以對已有函數進行額外的功能擴展。

  • 裝飾器的語法格式:

    # 裝飾器
    # def decorator(fn): # fn:被裝飾的目標函數.
    #     def inner():
    #         '''執行函數之前'''
    #         fn() # 執行被裝飾的目標函數
    #         '''執行函數之後'''
    #     return inner
    
  • 裝飾器的語法糖用法: @裝飾器名稱,同樣可以完成對已有函數的裝飾操作。

3、裝飾器的使用

學習目標

  • 能夠說出裝飾器的作用

1. 裝飾器的使用場景

  1. 函數執行時間的統計
  2. 輸出日誌信息

2. 裝飾器實現已有函數執行時間的統計

import time

# 裝飾器函數
def get_time(func):
    def inner():
        begin = time.time()
        func()
        end = time.time()
        print("函數執行花費%f" % (end-begin))
    return inner


@get_time
def func1():
    for i in range(100000):
        print(i)


func1()

執行結果:

...
99995
99996
99997
99998
99999
函數執行花費0.329066

2. 小結

通過上面的示例代碼可以得知裝飾器的作用:

  • 在不改變已有函數源代碼及調用方式的前提下,對已有函數進行功能的擴展。

4、通用裝飾器的使用

學習目標

  • 能夠寫出通用的裝飾器

1. 裝飾帶有參數的函數

# 添加輸出日誌的功能
def logging(fn):
    def inner(num1, num2):
        print("--正在努力計算--")
        fn(num1, num2)

    return inner


# 使用裝飾器裝飾函數
@logging
def sum_num(a, b):
    result = a + b
    print(result)


sum_num(1, 2)

運行結果:

--正在努力計算--
3

2. 裝飾帶有返回值的函數

# 添加輸出日誌的功能
def logging(fn):
    def inner(num1, num2):
        print("--正在努力計算--")
        result = fn(num1, num2)
        return result
    return inner


# 使用裝飾器裝飾函數
@logging
def sum_num(a, b):
    result = a + b
    return result


result = sum_num(1, 2)
print(result)

運行結果:

--正在努力計算--
3

3. 裝飾帶有不定長參數的函數

# 添加輸出日誌的功能
def logging(fn):
    def inner(*args, **kwargs):
        print("--正在努力計算--")
        fn(*args, **kwargs)

    return inner


# 使用語法糖裝飾函數
@logging
def sum_num(*args, **kwargs):
    result = 0
    for value in args:
        result += value

    for value in kwargs.values():
        result += value

    print(result)

sum_num(1, 2, a=10)

運行結果:

--正在努力計算--
13

4. 通用裝飾器

# 添加輸出日誌的功能
def logging(fn):
    def inner(*args, **kwargs):
        print("--正在努力計算--")
        result = fn(*args, **kwargs)
        return result

    return inner


# 使用語法糖裝飾函數
@logging
def sum_num(*args, **kwargs):
    result = 0
    for value in args:
        result += value

    for value in kwargs.values():
        result += value

    return result


@logging
def subtraction(a, b):
    result = a - b
    print(result)

result = sum_num(1, 2, a=10)
print(result)

subtraction(4, 2)

運行結果:

--正在努力計算--
13
--正在努力計算--
2

5. 小結

  • 通用裝飾器的語法格式:

    # 通用裝飾器
    def logging(fn):
      def inner(*args, **kwargs):
          print("--正在努力計算--")
          result = fn(*args, **kwargs)
          return result
    
      return inner
    

5、多個裝飾器的使用


1. 示例代碼

def make_div(func):
    """對被裝飾的函數的返回值 div標籤"""
    def inner():
        return "<div>" + func() + "</div>"
    return inner


def make_p(func):
    """對被裝飾的函數的返回值 p標籤"""
    def inner():
        return "<p>" + func() + "</p>"
    return inner


# 裝飾過程: 1 content = make_p(content) 2 content = make_div(content)
# content = make_div(make_p(content))
@make_div
@make_p
def content():
    return "人生苦短"

result = content()

print(result)

代碼說明:

  • 多個裝飾器的裝飾過程是: 離函數最近的裝飾器先裝飾,然後外面的裝飾器再進行裝飾,由內到外的裝飾過程

2. 小結

  • 多個裝飾器可以對函數進行多個功能的裝飾,裝飾順序是由內到外的進行裝飾

6、帶有參數的裝飾器介紹

帶有參數的裝飾器就是使用裝飾器裝飾函數的時候可以傳入指定參數,語法格式: @裝飾器(參數,…)

錯誤寫法:

def decorator(fn, flag):
    def inner(num1, num2):
        if flag == "+":
            print("--正在努力加法計算--")
        elif flag == "-":
            print("--正在努力減法計算--")
        result = fn(num1, num2)
        return result
    return inner


@decorator('+')
def add(a, b):
    result = a + b
    return result

result = add(1, 3)
print(result)

執行結果:

Traceback (most recent call last):
  File "/home/python/Desktop/test/hho.py", line 12, in <module>
    @decorator('+')
TypeError: decorator() missing 1 required positional argument: 'flag'

代碼說明:

  • 裝飾器只能接收一個參數,並且還是函數類型。

正確寫法:

在裝飾器外面再包裹上一個函數,讓最外面的函數接收參數,返回的是裝飾器,因爲@符號後面必須是裝飾器實例。

# 添加輸出日誌的功能
def logging(flag):

    def decorator(fn):
        def inner(num1, num2):
            if flag == "+":
                print("--正在努力加法計算--")
            elif flag == "-":
                print("--正在努力減法計算--")
            result = fn(num1, num2)
            return result
        return inner

    # 返回裝飾器
    return decorator


# 使用裝飾器裝飾函數
@logging("+")
def add(a, b):
    result = a + b
    return result


@logging("-")
def sub(a, b):
    result = a - b
    return result

result = add(1, 2)
print(result)

result = sub(1, 2)
print(result)

小結

  • 使用帶有參數的裝飾器,其實是在裝飾器外面又包裹了一個函數,使用該函數接收參數,返回是裝飾器,因爲 @ 符號需要配合裝飾器實例使用

7、類裝飾器的使用

學習目標

  • 能夠知道類裝飾器的使用

1. 類裝飾器的介紹

裝飾器還有一種特殊的用法就是類裝飾器,就是通過定義一個類來裝飾函數。

類裝飾器示例代碼:

class Check(object):
    def __init__(self, fn):
        # 初始化操作在此完成
        self.__fn = fn

    # 實現__call__方法,表示對象是一個可調用對象,可以像調用函數一樣進行調用。
    def __call__(self, *args, **kwargs):
        # 添加裝飾功能
        print("請先登陸...")
        self.__fn()


@Check
def comment():
    print("發表評論")


comment()

代碼說明:

  • @Check 等價於 comment = Check(comment), 所以需要提供一個init方法,並多增加一個fn參數。
  • 要想類的實例對象能夠像函數一樣調用,需要在類裏面使用call方法,把類的實例變成可調用對象(callable),也就是說可以像調用函數一樣進行調用。
  • call方法裏進行對fn函數的裝飾,可以添加額外的功能。

執行結果:

請先登陸...
發表評論

2. 小結

  • 想要讓類的實例對象能夠像函數一樣進行調用,需要在類裏面使用call方法,把類的實例變成可調用對象(callable)
  • 類裝飾器裝飾函數功能在call方法裏面進行添加
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章