閉包函式,裝飾器詳解

silence^發表於2024-04-17

閉包函式

【1】.閉包函式兩大特徵

1.閉:定義在函式內部的函式

2.包:內部函式使用了外層函式名稱空間中的名字

【2】在程式碼中怎麼走

def outer():
    def inner():
        print(f'真簡單')
    return inner

res = outer()

在上述程式碼中怎麼走

  • 第一步定義outer函式,什麼時候執行這個函式里的程式碼,要遇到outer()才會執行,

  • 第二步到res = outer(),=是賦值符號所以先看outer()

  • 第三步有了outer()才會執行裡面的程式碼,進入到裡面執行def inner():

  • 第四步因為沒有inner()所以inner函式里面的程式碼不會執行,只會返回一個inner函式名

  • 第五步,然後把inner這個函式名給了res這個變數,使得res指向了inner函式里的函式體

    • 然後res()就相當於inner()
def outer():
    def inner():
        print(f'真簡單')
    return inner

res = outer()
res() # 真簡單

現在inner()是不是閉包函式

def outer():
    def inner():
        print(f'真簡單')
    return inner
# 現在只是只是在outer函式內部,並沒有使用了外層函式名稱空間中的名字

def outer():
    x = 741
    def inner():
        print(f'真簡單',x)
    return inner
# 現在inner算是真正的閉包函式

執行程式碼順序

# 最後輸出999還是741
def outer():
    x = 741
    def inner():
        print(f'真簡單',x)
    return inner
x=999
res = outer()
res()  # 真簡單 741
# inner是在outer 區域性裡面的的區域性裡面的,inner本身沒有x,所以他會往上找,就找到outer區域性裡面的x=741,跟x=999無關

【3】閉包函式實際應用

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

函式體傳參方式一:形參


def index(username):
    print(username)   
 
# 函式體程式碼需要什麼就可以在形參中寫什麼
index('silence')  # silence # 在呼叫index這個函式時就必須給他一個username引數

函式體傳參方式二:閉包

def outer():
    username = 'silence'
    def index():
        print(username) # 永遠使用的都是silence
    return index

res = outer()
res()    # silence

多個傳遞

def outer(username):
    # username = 'silence'
    def index():
        print(username)
    return index

res = outer('silence')
#形參username與值silence臨時繫結>>>放在了outer區域性名稱空間中
res()  # silence
rest = outer('happy')
rest() # happy

程式碼執行順序圖

裝飾器

【1】簡介

裝飾器是由名稱空間,函式名,閉包函式等整合到一起的產物

【2】裝飾器定義

不改變被裝飾物件原有的呼叫方式內部程式碼的情況下

給被裝飾物件新增新功能

【3】裝飾器原則

對擴充套件開發,對修改封閉

【4】裝飾器瞭解

import  time
print(time.time()) #1713259590.5478706
# 上述數字是時間戳:1970年1月1日0時0分0秒距離剛剛程式碼執行的秒數


import  time
time.sleep(4)  # 讓程式原地等待四秒
print("你真棒")


import  time

def index():
    time.sleep(3)
    print('from index')
star_time = time.time() # 函式執行之前獲得一個時間戳
index()
end_time = time.time() # 函式執行之後獲得一個時間戳
print(end_time - star_time)  # 兩個時間戳的差值就是函式的執行時間
# from index 3.000314950942993 

【5】程式碼怎麼生成一個裝飾器

封裝成函式之後,呼叫方式改變了,不符合裝飾器原則

#缺陷一:
    # 程式碼寫死了,無法統計其他函式的執行時間 只有index()
    # 如何解決:----> 將函式名透過形參的形式傳入,例如get_time(in)
# 缺陷二:    
    # 封裝成函式後 以下程式碼中 index()呼叫方式改變成了get_time(index)
    # 透過形參的形式傳入解決不掉
import  time

def index():
    time.sleep(3)
    print('from index')

def get_time():
    star_time = time.time() # 函式執行之前獲得一個時間戳
    index()
    end_time = time.time() # 函式執行之後獲得一個時間戳
    print(end_time - star_time)  # 兩個時間戳的差值就是函式的執行時間

get_time()

裝飾器簡易版本(無參裝飾器)

import  time
def index():
    time.sleep(3)
    print('from index')
def outer(func):
    def get_time():
        star_time = time.time() # 函式執行之前獲得一個時間戳
        func()  # 呼叫了真正的index函式
        end_time = time.time() # 函式執行之後獲得一個時間戳
        print(end_time - star_time)  # 兩個時間戳的差值就是函式的執行時間
    return get_time  

index = outer(index)
index() # 看似呼叫的是index其實呼叫的是get_time
print(index)  # 全域性名稱空間中的index指向的是get_time函式體程式碼

執行順序

裝飾器進階版本(有參裝飾器)

解決的是引數問題

程式報錯

import  time
def home(a):
    time.sleep(3)
    print('from home')
def outer(func_name):
    def get_time():
        star_time = time.time()
        func_name()
        end_time = time.time()
        print(end_time - star_time)
    return get_time

home = outer(home)
home() # 報錯 home() missing 1 required positional argument: 'a'

函式體程式碼裡需要一個引數,有兩種辦法

  • 方式一:直接形參傳
  • 方式二:再進行一次閉包操作
import  time
def home(a):
    time.sleep(3)
    print('from home')
def outer(func_name):
    def get_time(a):     # 意思就是在呼叫get_time時候也要給他傳遞一個引數
        star_time = time.time()
        func_name(a)
        end_time = time.time()
        print(end_time - star_time)
    return get_time

home = outer(home)
home('silence')     # home就是呼叫的get_time 所以需要一個引數'silence'
# from home
# 3.011603593826294

我們根本不知道即將執行的函式是否需要引數但是還想讓他正常執行,於是


import  time
def index():
    time.sleep(2)
    print("from index")
def home(a):
    time.sleep(3)
    print('from home')
def outer(func_name):
    def get_time(*arga,**kwargs):
        star_time = time.time()
        func_name(*arga,**kwargs)
        end_time = time.time()
        print(end_time - star_time)
    return get_time

home = outer(home)
home('silence')
index = outer(index)
index()
# from home
# 3.008333921432495
# from index
# 2.011590003967285

圖解

完整版直譯器

解決的是返回值問題

import  time
def index(a,b):
    time.sleep(2)
    print(a, b, "from index")
    return '加油!'

def outer(func_name):
    def get_time(*arga,**kwargs):
        star_time = time.time()
        func_name(*arga,**kwargs)
        end_time = time.time()
        print(end_time - star_time)
    return get_time


index = outer(index) # 接收函式的返回值
res = index(11,22)
print(res)
# 11 22 from index
# 2.0102128982543945
# None

為什麼是返回None,其實是拿到的是get_time的返回值,但是get_time沒有返回值,所以是None

想讓index有返回值怎麼辦

import  time
def index(a,b):
    time.sleep(2)
    print(a, b, "from index")
    return '加油!'

def outer(func_name):
    def get_time(*arga,**kwargs):
        star_time = time.time()
        res=func_name(*arga,**kwargs)  # 執行真正的index函式
        end_time = time.time()
        print(end_time - star_time)
        return res
    return get_time


index = outer(index) # 接收函式的返回值
res = index(11,22)
print(res)

裝飾器模板

# 萬能模板
def outer(func_name): # func_name 用於接收被裝飾的物件(函式)
    def inner(*args, **kwargs):
        print('執行被裝飾函式之前,可以做的額外操作')
        res = func_name(*args, **kwargs) # 執行真正的被裝飾函式
        print('執行被裝飾函式後,可以做的額外操作')
        return res # 返回真正函式的返回值
    return inner

例題:驗證登陸

def home(*args, **kwargs):
    print('我是home函式,只有silence才能呼叫')

def outer(func_name):
    def inner(*args, **kwargs):
        # 執行home前先獲取使用者資料
        username = input('username>>>:').strip()
        password = input('password>>>:').strip()
        if username == 'silence' and password == '741':
            res = func_name(*args, **kwargs) # 執行真正的被裝飾函式
            return res  # 返回真正函式的返回值
        else:
            print('你沒有資格執行')
    return inner
home = outer(home)
home()
# username>>>:silence
# password>>>:741
# 我是home函式,只有silence才能呼叫

或者是

def index():
    print("Hello")


def outer(func_name):
    def inner(*args, **kwargs):
        # 執行home前先獲取使用者資料
        username = input('username>>>:').strip()
        password = input('password>>>:').strip()
        if username == 'silence' and password == '741':
            res = func_name(*args, **kwargs) # 執行真正的被裝飾函式
            return res  # 返回真正函式的返回值
        else:
            print('你沒有資格執行')
    return inner
# home = outer(home)
# home()
index = outer(index)
index()
# username>>>:silence
# password>>>:741
# Hello

裝飾器語法糖

僅僅是讓程式碼編寫的更加好看,簡潔

def outer(func_name):
    def inner(*args, **kwargs):
        print('執行函式之前的操作')
        res = func_name(*args, **kwargs)
        return res
    return inner


@outer   # 寫在想要裝飾的函式的頭上   等價於index = outer(index)
def index(*args, **kwargs):
    print('from index')
@outer   # 等價於home = outer(home)
def home():
    print('from home')
# index = outer(index)  # 總感覺這一行程式碼有些low
index()
home()
# 執行函式之前的操作
# from index
# 執行函式之前的操作
# from home

語法糖內部原理

1.使用時最好跟在被裝飾物件的上方

2.語法糖會自動將下面緊挨著的函式名傳給@後面的函式呼叫

裝飾器修復技術

1. help可以檢視指定函式的註釋資訊

ef outer(func_name):
    def inner(*args, **kwargs):
        print('執行被裝飾器之前可以做的操作')
        res = func_name(*args, **kwargs)
        return res
    return inner


@outer   # 寫在想要裝飾的函式的頭上   等價於index = outer(index)
def index(*args, **kwargs):
    print('from index')
   # 等價於home = outer(home)
def home():
    '''這是一個註釋'''
    print('from home')
print(help(home)) 
help(len)
# home()
    # 這是一個註釋
    
# len(obj, /)
    # Return the number of items in a container.

2. 裝飾器修復

語法固定搭配

  • from functools import wraps
    • @wraps(func_name)
from functools import wraps
def outer(func_name):  # func_name 用於接收被裝飾的物件(函式)
    @wraps(func_name)
    def inner(*args, **kwargs):
        print('執行被裝飾器之前可以做的操作')
        res = func_name(*args, **kwargs)
        return res
    return inner


@outer   # 寫在想要裝飾的函式的頭上   等價於index = outer(index)
def index(*args, **kwargs):
    print('from index')
   # 等價於home = outer(home)
@outer
def home():
    print('from home')

index()
home()

相關文章