13. 閉包函式與裝飾器

hbutmeng發表於2024-07-31

1. 函式名的多種用法

1.1 方法一:函式名可以當成變數名賦值

def f1(x, y):
    print(x + y)

fn = f1
fn(11, 22)  # 33

1.2 方法二:函式名可以作為容器型別的元素

def f1(x, y):
    print(x + y)

func_dict = {'Add': f1}
func_dict.get('Add')(33, 66)  # 99

1.3 方法三:函式名可以作為引數傳遞給另一個函式使用

def f1(x, y):
    return x + y

def f2(a, b, func):
    return func(a, b) + a + b

result = f2(a=1, b=2, func=f1)
print(result)  # 6

1.4 方法四:函式名可以作為函式的返回值

def f1(x, y):
    return x + y

print(id(f1))

def f2():
    return f1

print(id(f2()))

2. 閉包函式

2.1 概念

外層函式內部定義一個裡層函式,並且裡層函式用到了外層函式的變數

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

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

def outer():
    a = 666
    def inner():
        print('裡層函式', a)  # 裡層函式使用了外層函式名稱空間中的名字
    inner()
outer()

2.2 閉包函式的應用場景

閉包傳參方式1:外層函式的變數值固定

def outer():
    a = 666
    def inner():
        print('裡層函式', a)  # 裡層函式使用了外層函式名稱空間中的名字
    return inner

res = outer()
res()  # 裡層函式 666        相當於inner()

閉包傳參方式2:外層函式的變數值不固定

def outer(name):
    #  當有實參傳入時,name=實參
    def inner():
        print('裡層函式', name)  # 裡層函式使用了外層函式名稱空間中的名字
    return inner

res = outer('leomessi')
res()  # 裡層函式 leomessi     相當於inner()

3. 裝飾器

3.1 基礎概念

定義:

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

裝飾器的原則

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

key word:

名稱空間、函式名、閉包函式

裝飾器的分類:

無參裝飾器、有參裝飾器

3.2 推導

需求:計算函式的執行時間

# 預備知識:time模組
import time
print(time.time())  # 時間戳,是從1970年1月1日(UTC/GMT的午夜)開始所經過的秒數(不考慮閏秒),用於表示一個時間點。

實現方法1:

沒有修改被裝飾物件的呼叫方式,也沒修改內部程式碼,但是可擴充套件性差

import time

def f1():
    start = time.time()
    time.sleep(3)
    end = time.time()
    print(f'該函式執行耗時{end - start}s')  # 該函式執行耗時3.0012145042419434s
f1()

def f2():
    start = time.time()
    time.sleep(3)
    end = time.time()
    print(f'該函式執行耗時{end - start}s')  # 該函式執行耗時3.01076340675354s
f2()

實現方法2:

在很多地方都要計算f1、f2執行時間,就用到了函式

將函式名透過形參傳入,封裝成函式之後,呼叫方式變了,不符合裝飾器原則

import time
def f1():
    time.sleep(3)
    print('函式f1執行')

def f2():
    time.sleep(3)
    print('函式f2執行')

def cal(func):
    start = time.time()
    func()
    end = time.time()
    print(f'函式{func}執行耗時{end - start}s')

cal(f1)
# 函式f1執行
# 函式<function f1 at 0x000001F911B23E20>執行耗時3.0101499557495117s

cal(f2)
# 函式f2執行
# 函式<function f2 at 0x000001A3B73F3370>執行耗時3.015449047088623s

實現方法3:

方法2給函式體直接傳參無法實現裝飾器,於是採用另一種給函式體傳參的方式(閉包函式)

閉包函式的方式外層函式的變數值固定,無法將其它函式名傳入,可擴充套件性差

import time
def f1():
    time.sleep(3)
    print('函式f1執行')

def outer():
    func = f1  # 真正的f1被outer區域性名稱空間儲存了

    def inner():
        start = time.time()
        func()  # 呼叫了真正的f1函式
        end = time.time()
        print(f'函式{func}執行耗時{end - start}s')
    return inner

res = outer()
res()
# 函式f1執行
# 函式<function f1 at 0x0000025DCD183E20>執行耗時3.0119853019714355s

這種方式也不是真正的裝飾器,需要呼叫res函式才能呼叫f1函式

實現方法4:

為了解決方法3外層函式變數值固定的問題,採用向外層函式傳參的方式

將函式名透過形參傳入,增強可擴充套件性

import time
def f1():
    time.sleep(3)
    print('函式f1執行')

def f2():
    time.sleep(3)
    print('函式f2執行')

def outer(func):
    def inner():
        start = time.time()
        func()  # 呼叫了真正的f1函式
        end = time.time()
        print(f'函式{func}執行耗時{end - start}s')
    return inner

# f1 = outer(f1)  # 左側的f1就是一個普通的變數名
# f1()  # 看似呼叫的是f1,其實呼叫的是inner
# 函式f1執行
# 函式<function f1 at 0x0000021BEC373E20>執行耗時3.0068602561950684s

f2 = outer(f2)  # 左側的f2就是一個普通的變數名
f2()  # 看似呼叫的是f2,其實呼叫的是inner

f1 = outer(f1)實現了裝飾器,f1()即可呼叫原f1函式,右邊的f1是原函式名,存到outer的區域性名稱空間了,

左邊的f1只是一個普通的變數名

小結:

outer()---返回值是inner的函式地址---將inner的函式名重新命名為f1或f2

f1=outer(f1)---f1就是outer內inner的記憶體地址

f1()---outer內的inner執行---func()---f1真正的函式記憶體地址---f1()

3.3 練習

# 練習:
"""
先定義一個存款、取款函式,使用者登入過才能存款、取款,沒有登入讓先登入
不能改變原本存款、取款的呼叫方式
"""
def deposit():
    print('存款中...')
    return True, '取款完成'

def withdraw():
    print('存款中...')
    return True, '取款完成'

login_dict = {'username': None, 'status': None}

def outer(func):
    def inner():
        # 在執行withdraw功能之前進行驗證是否登入
        if login_dict.get('username') is not None:
            res = func()  # 接收真正函式的返回值
            return res  # 條件為真,將真正函式的返回值返回
        else:
            return False, '請先登入'  # 條件為假,讓使用者先登入
    return inner
withdraw
= outer(withdraw) res2 = withdraw() print(res2) deposit = outer(deposit) res3 = deposit() print(res3)
# 多層驗證,先驗證使用者名稱,再驗證密碼
def outer(func):
    def inner():
        # 在執行withdraw功能之前進行驗證是否登入成功
        if login_dict.get('username') != 'messi':
            return False, '使用者名稱錯誤'
        elif login_dict.get('password') != '001':
            return False, '請先登入'  # 密碼錯誤
        else:
            res = func()
            return res
    return inner

withdraw = outer(withdraw)
res2 = withdraw()
print(res2)

3.4 無參裝飾器模板

def f1():
    pass

def outer(func):
    def inner():
     執行真正函式之前,可以做的額外操作
# 其它如登入認證等操作一般放在執行真正函式之前 result = func() # 執行真正的函式

執行真正函式之後,可以做的額外操作
     對返回的結果進行處理返回 return result # 返回真正函式的返回值 return inner f1 = outer(f1) f1()

3.5 有參裝飾器

引入

login_dict = {'name': 'messi', 'pwd': '001'}

def withdraw(name, money):
    print(f'使用者{name}正在取款{money}元')

def outer(func):
    def inner(name, money):
        if login_dict.get('name'):
            return func(name, money)
        else:
            return False, '請先登入'
    return inner

withdraw = outer(withdraw)
withdraw('messi', 10)  # 使用者messi正在取款10元

可變長引數,相容所有函式的引數

login_dict = {'name': 'messi', 'pwd': '001'}

def withdraw(name, money):
    print(f'使用者{name}正在取款{money}元')

def transfer(name, to_other, money):
    print(f'使用者{name}給{to_other}轉賬了{money}元')

def outer(func):
    def inner(*args, **kwargs):
        if login_dict.get('name'):
            return func(*args, **kwargs)
        else:
            return False, '請先登入'
    return inner

withdraw = outer(withdraw)
withdraw('messi', 10)  # 使用者messi正在取款10元

transfer = outer(transfer) transfer('ronaldo', 'kylian', 20) # 使用者ronaldo給kylian轉賬了20元

3.6 有參裝飾器模板

def f1(*args, **kwargs):
    pass

def outer(func):  # func用於接收被裝飾的物件(函式)
    def inner(*args, **kwargs):
        print('執行真正函式之前,可以做的額外操作')
        res = func(*args, **kwargs)  # 執行真正的被裝飾函式,res的作用是接收原函式被呼叫後的返回值
        print('執行真正函式之後,可以做的額外操作')
對返回的結果進行處理返回
return res # 返回真正函式的返回值 return inner f1 = outer(f1) f1()

4. 語法糖

4.1 原理:

1.使用的時候放在真正函式的上方

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

login_dict = {'name': 'messi', 'pwd': '001'}
def outer(func):
    def inner(*args, **kwargs):
        if login_dict.get('name'):
            return func(*args, **kwargs)
        else:
            return False, '請先登入'
    return inner

@outer  # 相當於:withdraw = outer(withdraw)
def withdraw(name, money):
    print(f'使用者{name}正在取款{money}元')

@outer  # 相當於:transfer = outer(transfer)
def transfer(name, to_other, money):
    print(f'使用者{name}給{to_other}轉賬了{money}元')

4.2 多層語法糖

4.2.1 多層有參裝飾器

login_dict = {'name': 'messi', 'role': 'admin'}

def login(func):
    # func是withdraw函式的記憶體地址
    def inner(*args, **kwargs):
        print('驗證登入程式')
        if not login_dict.get('name'):
            return '請先登入'
        else:
            return func(*args, **kwargs)
    return inner

def permission(func):
    # func是login的inner函式地址
    def inner(*args, **kwargs):
        print('驗證許可權程式')
        if login_dict.get('role') != 'admin':
            return '當前許可權不能訪問該功能'
        return func(*args, **kwargs)
    return inner

def withdraw(name, money):
    print(f'使用者{name}正在取款{money}元')

# 必須是登入狀態且角色是管理員才能取款
withdraw = login(withdraw)  # =左邊是一個普通的變數名,右邊是login函式返回值(inner函式記憶體地址)
withdraw = permission(withdraw)  # 等價於withdraw = permission(login的inner函式地址)
# 上述語句在執行時,
# 第一步:permission(login的inner)
# 第二步:在permission的裡層函式中func為login的inner函式,因此進入到login函式中校驗登入
# 第三步:進入login函式執行inner函式,校驗登入,執行func,func為withdraw函式的記憶體地址
# 第四步:執行真正的withdraw
withdraw('ronaldo', 20)
# 驗證許可權程式
# 驗證登入程式
# 使用者ronaldo正在取款20元
# 上述程式碼按從上到下的順序編寫,先包login,再包permission,但是執行時先驗證許可權,再驗證登入,因此要將登入和許可權互換位置


# #  這種情況下就是先驗證登入,再驗證許可權
# withdraw = permission(withdraw)
# withdraw = login(withdraw)
# withdraw('ronaldo', 20)

4.2.2 多層語法糖

多層語法糖的包裝順序是:從下往上包裝⬆

執行順序是:從上往下執行⬇

login_dict = {'name': 'messi', 'role': 'admin'}
def login(func):
    # func是withdraw函式的記憶體地址
    def inner(*args, **kwargs):
        print('驗證登入程式')
        if not login_dict.get('name'):
            return '請先登入'
        else:
            return func(*args, **kwargs)
    return inner


def permission(func):
    # func是login的inner函式地址
    def inner(*args, **kwargs):
        print('驗證許可權程式')
        if login_dict.get('role') != 'admin':
            return '當前許可權不能訪問該功能'
        return func(*args, **kwargs)
    return inner


@login  # withdraw = login(withdraw)  左邊with普通函式名,右邊with取自上一步,是permission的inner函式
@permission  # withdraw=permission(withdraw)  左邊withdraw普通變數名,右邊真正函式,執行結果是permission的inner函式
def withdraw(name, money):
    print(f'使用者{name}正在取款{money}元')
# 上述語法糖含義:先包permission,再包login

# 呼叫函式時,以@login為參考,這裡withdraw等價於login(permission中inner函式地址)
withdraw('messi', 10)

4.3 有參語法糖

有參的有參裝飾器

login_dict = {'name': 'messi', 'role': 'admin'}
# 定義一個函式,當傳入引數為login時,返回login登入驗證函式 # 當傳入引數為permission時,返回permission許可權驗證函式 def choice(status): if status == 'login': def login(func): # func是withdraw函式的記憶體地址 def inner(*args, **kwargs): print('驗證登入程式') if not login_dict.get('name'): return '請先登入' else: return func(*args, **kwargs) return inner return login elif status == 'permission': def permission(func): # func是login的inner函式地址 def inner(*args, **kwargs): print('驗證許可權程式') if login_dict.get('role') != 'admin': return '當前許可權不能訪問該功能' return func(*args, **kwargs) return inner return permission def withdraw(name, money): print(f'使用者{name}正在取款{money}元') # 以下僅驗證登入 # login_a = choice('login') # 拿到login函式 # withdraw = login_a(withdraw) # withdraw('messi', 10) # 驗證登入程式 # 使用者messi正在取款10元 # 先驗證登入再驗證許可權 login_a = choice('login') permission_a = choice('permission') withdraw = permission_a(withdraw) withdraw = login_a(withdraw) withdraw('ronaldo', 20) # 驗證登入程式 # 驗證許可權程式 # 使用者ronaldo正在取款20元

上述程式碼改寫為有參語法糖

login_dict = {'name': 'messi', 'role': 'admin'}

# 定義一個函式,當傳入引數為login時,返回login登入驗證函式
#            當傳入引數為permission時,返回permission許可權驗證函式
def choice(status):
    if status == 'login':
        def login(func):
            # func是withdraw函式的記憶體地址
            def inner(*args, **kwargs):
                print('驗證登入程式')
                if not login_dict.get('name'):
                    return '請先登入'
                else:
                    return func(*args, **kwargs)
            return inner
        return login
    elif status == 'permission':
        def permission(func):
            # func是login的inner函式地址
            def inner(*args, **kwargs):
                print('驗證許可權程式')
                if login_dict.get('role') != 'admin':
                    return '當前許可權不能訪問該功能'
                return func(*args, **kwargs)
            return inner
        return permission

@choice('login')
@choice('permission')
def withdraw(name, money):
    print(f'使用者{name}正在取款{money}元')

withdraw('kylian', 20)

驗證登入程式
驗證許可權程式
使用者kylian正在取款20元

 

相關文章