1. 程式人生 > 其它 >python閉包函式與裝飾器

python閉包函式與裝飾器

目錄

閉包函式

閉包概念

  • 閉:定義在函式內部的函式
  • 包:內部函式使用了外層函式名稱空間中的名字
# 閉包函式
def outer():
    x = 111
    # 定義在函式內部的函式
    def inner():
        # 使用了外層函式名稱空間中的名字
        print(x)
    return inner
x = 666
res = outer()
res()  # 輸出:111  

實際應用

閉包函式是給函式體傳參的另外一種方式

def outer(username):
    def index():
        print(username)
    return index
res = outer('kevin')  # 形參username與值kevin臨時繫結
res()  # 輸出:kevin
res1 = outer('jason')  # 形參username與值jason臨時繫結
res1()  # 輸出:jason

這時候有人就很迷惑了,明明只要定義一個函式index()就可以了,為什麼要用outer()包起來傳參呢?別急,這是為接下來的內容做鋪墊用的。

裝飾器

簡介

裝飾器並不是一個新的知識點,而是由前面所有的函式知識點整合到一起的產物。

裝飾器的本質:在不改變被裝飾物件原有的“呼叫方式”和“內部程式碼”的情況下給被裝飾物件新增新的功能。

這是不是聽起來有些神奇?裝飾器的原則就是對擴充套件開放,對修改封閉。

簡單版本裝飾器

瞭解完什麼是裝飾器之後,我們先學習使用簡單版本的裝飾器。

現在有一段程式碼:

import time  # 匯入模組,不用管什麼意思
def index():
    time.sleep(1)  # 讓程式休眠1秒鐘
    print('這裡是index函式')

def home():
    time.sleep(1)  # 讓程式休眠1秒鐘
    print('這裡是home函式')

我如何能在不動上面2個函式的情況下,新增一個功能:輸出函式執行的時長。

這時候有人就要說了:啊?這不是很簡單,我只要新增一個函式

def get_time(function_name):
    start_time = time.time()  # 獲取執行函式前的時間
    function_name()  # 呼叫函式
    end_time = time.time()  # 獲取執行結束後的時間
    print(end_time - start_time)  # 輸出時間差

get_time(index)
get_time(home)

這個方法確實是可以,但是執行函式都要呼叫get_time()。

所以彆著急,裝飾器的功能還沒了解全之前,請先繼續看下去。

實現:

import time  # 匯入模組,不用管什麼意思

# 裝飾物件
def index():
    time.sleep(1)  # 讓程式休眠1秒鐘
    print('這裡是index函式')

def home():
    time.sleep(1)  # 讓程式休眠1秒鐘
    print('這裡是home函式')
    
# 裝飾器
def outer(function_name):
    def inner():
        # 獲取執行函式前的時間
        start_time = time.time()  
        # 呼叫名為function_name函式
        function_name()  
        # 獲取函式執行結束後的時間
        end_time = time.time()  
        # 輸出時間差
        print(end_time - start_time)  
	# 返回內部函式名
    return inner
# 讓index()變成inner()
index = outer(index)
index()  
"""
輸出:
這裡是index函式
1.0101792812347412
"""

# 狸貓換太子
home = outer(home)
home()  
"""
輸出:
這裡是home函式
1.0047881603240967
"""

這個時候函式只需要呼叫它自己就能輸出執行時間了,相當於給函式內部添加了一段程式碼一樣。

進階版本裝飾器

這時候如果函式有引數該怎麼做呢?並且引數還不知道幾個?

很簡單,只要加上可變長形參就行了。

實現:

import time

def index(a):  # 有引數
    time.sleep(1) 
    print('這裡是index函式', a)

def home():  # 無引數
    time.sleep(1)  
    print('這裡是home函式')
    
def outer(function_name):
    def inner(*args, **kwargs):  # 新增可變長形參
        start_time = time.time()  
        function_name(*args, **kwargs)  # 傳參
        end_time = time.time()  
        print(end_time - start_time)  
    return inner
index = outer(index)
index(555)  
"""
輸出:
這裡是index函式 555
1.0047197341918945
"""

home = outer(home)
home()  
"""
輸出:
這裡是home函式
1.0003490447998047
"""

完整版本裝飾器

這個時候問題又出現了,如果函式有返回值又該怎麼辦?

也很簡單,在裝飾器內部函式加一個return返回值就行了。

實現:

import time

def index(a):
    time.sleep(1) 
    print('這裡是index函式')
    return a  # 有返回值

def home():
    time.sleep(1)  
    print('這裡是home函式')
    
def outer(function_name):
    def inner(*args, **kwargs): 
        start_time = time.time()  
        # 將函式返回值賦值給res
        res = function_name(*args, **kwargs)  
        end_time = time.time()  
        print(end_time - start_time)  
        """返回函式的返回值"""
        return res
    return inner
index = outer(index)
print(index(555))
"""
輸出:
這裡是index函式
1.0073633193969727
555
"""

home = outer(home)
home()  
"""
輸出:
這裡是home函式
1.0051474571228027
"""

裝飾器模板

還沒搞明白裝飾器怎麼回事?小問題!這裡有一套萬能模板,只要知道怎麼使用裝飾器就行了。

'''編寫裝飾器其實有一套固定的程式碼 不需要做任何理解'''
def outer(func_name):  # func_name用於接收被裝飾的物件(函式)
    def inner(*args, **kwargs):
        print('執行被裝飾函式之前 可以做的額外操作')
        res = func_name(*args, **kwargs)  # 執行真正的被裝飾函式
        print('執行被裝飾函式之後 可以做的額外操作')
        return res  # 返回真正函式的返回值
    return inner

裝飾器語法糖

由於感覺在這塊程式碼太繁瑣了

index = outer(index)
index()  

於是python給了一個方法,用@+裝飾器名稱放在裝飾物件的上方。

實現:

def outer(func_name):  
    def inner(*args, **kwargs):
        # 額外操作
        res = func_name(*args, **kwargs) 
        # 額外操作
        return res
    return inner

# 裝飾器語法糖
@outer
def index():
    print('這裡是index函式')

"""可以直接使用index(),省去了一個步驟"""
index()

語法糖內部原理:

  1. 使用的時候最好緊跟在被裝飾物件的上方
  2. 語法糖會自動將下面緊挨著的函式名傳給@後面的函式呼叫

裝飾器修復技術

在瞭解裝飾器修復技術之前,我們先了解被裝飾物件的內部屬於誰了

def outer(func_name):  
    def inner(*args, **kwargs):
        res = func_name(*args, **kwargs) 
        return res
    return inner

@outer
def index():
    print('這裡是index函式')

print(index)
"""
輸出:
<function outer.<locals>.inner at 0x0000025BA5B9F268>
"""

可以看出,這時的index已經是inner的人了,這就讓人很不爽了,所以我們要把它恢復回來。

from functools import wraps  # 匯入模組
def outer(func_name):  
    @wraps(func_name)  # 修復
    def inner(*args, **kwargs):
        res = func_name(*args, **kwargs) 
        return res
    return inner

@outer
def index():
    print('這裡是index函式')

print(index)
"""
輸出:
<function index at 0x00000133924FF268>
"""

可以看到,index還是原來的index。