1. 程式人生 > 其它 >2022.3.18學習筆記

2022.3.18學習筆記

2022.3.18

  • 閉包函式
  • 閉包函式的實際應用
  • 裝飾器簡介
  • 簡易版本裝飾器
  • 進階版本裝飾器
  • 完整版本裝飾器
  • 裝飾器模板
  • 裝飾器語法糖
  • 裝飾器修復技術

一、閉包函式

1、什麼是閉包函式

兩大特徵:

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

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

def outer():
    a = 111
    inner():
        print(a)

如上程式碼所示,函式是巢狀型,符合定義在函式內部,同時內函式使用了外函式的變數a,因此這就是一個閉包函式,暫不考慮其他條件

2、閉包函式的應用

我們已經知道了閉包函式的兩大特徵,那麼閉包函式引用外部函式的名字有幾種形式呢

(1)在外層函式體程式碼尋找名字

def outer():
    a = 111
    def inner():
        print(a)
    return inner
res = outer()
res()  # 111

(2) 在外層函式括號內找引數

1.預設形參,相當於寫死
def outer(a=111):
    def inner():
        print(a)
    return inner
2.外部傳參
def outer(a):
	def inner():
        print(a)
    return inner
res = outer(111)
res()  # 111

以上是閉包函式的基本使用,首先要理解閉與包的概念,這其實也是給後面的內容做鋪墊。

二、裝飾器

1、裝飾器簡介

概念:裝飾器的本質其實是在不改變原有物件的呼叫方式和內部程式碼的前提下,加以修飾或新增新的功能

原則:對拓展開放、對修改封閉

這裡舉個例子,但是首先需要了解一個概念,時間戳

import time

print(time.time()) # 結果是一個時間戳

時間戳:

1970年1月1日0時0分0秒距離剛剛程式碼執行的間隔秒數

然後我們來看下面這個函式:

import time
def index():
    time.sleep(1)  # 表示後面的程式碼一秒鐘以後執行
    print(666)    

這個函式是表示停留一秒鐘列印666出來,那麼如果我們需要打印出來這個程式碼執行的時間,怎麼做呢?

import time
def index():
    time.sleep(1)  
    print(666) 
    
def test():
    start_time = time.time()
    index()  # 在函式test內呼叫index函式
    end_time = time.time()
    print(start_time - end_time)
test()  # 呼叫test函式

這樣看來我們重新定義一個函式,從而解決了這個需求,能夠通過test函式計算index函式體程式碼執行的時間,但是我們發現通過定義其他函式,對index函式進行了功能增加,但是不符合裝飾器的特徵,因為改變了呼叫方式

那麼如何不改變呼叫方式的情況下,呼叫index函式,同時增加功能呢,這裡就需要結合之前學過的閉包函式加以改良了。

2、簡易版本裝飾器

怎麼改良呢?結合閉包函式,我們可以將上面test函式封裝起來,外面再來一層函式

import time
def index():
    time.sleep(1)  
    print(666) 

def outer():
    def test():
        start_time = time.time()
        index()  # 在函式test內呼叫index函式
        end_time = time.time()
        print(start_time - end_time)
    return test  #將test函式名作為outer函式的返回值
res = outer()  # 將outer函式的返回值賦值給res
res()  # 加上括號呼叫res就相當於呼叫test

現在我們發現這樣的話我們還是通過res去呼叫的test從而呼叫的index,還是沒有解決呼叫方式的問題,同時內部的函式也不是閉包函式,因為只有閉沒有包,這時候就需要向內傳遞一個引數了。

import time
def index():
    time.sleep(1)  
    print(666) 

def outer(func):
    def test():
        start_time = time.time()
        func()  # 執行傳進來引數的函式
        end_time = time.time()
        print(start_time - end_time)
    return test
outer(index)  # 將index作為outer的引數傳進去
res = outer(index)  # 賦值給變數res,res接收outer的返回值test,因此res=test
res()  # 呼叫res,相當於test()

這樣的話,裡面的函式使用了外部純進來的引數,形成了一個閉包函式,同時傳進來的引數是index函式名,因此又激活了原index函式的呼叫,此時是用res呼叫test,看起來繞了一圈還是在用別的函式呼叫index,怎麼解決呢?

其實可以發現res是一個變數名,知識為了接收outer的返回值,那麼這個變數可不可以使用index呢,我們都知道變數賦值先看右邊在看左邊,那麼使用index的話,就可以使用index()來呼叫封裝好的test函數了,而test又是為了優化原來的index函式的,這樣就解決了呼叫方式的問題,我們來試試吧!

import time
def index():
    time.sleep(1)  
    print(666) 

def outer(func):
    def test():
        start_time = time.time()
        func()  # 執行傳進來引數的函式
        end_time = time.time()
        print(start_time - end_time)
    return test
index = outer(index)  # 相當於index = test
index()  # 相當於test()

這樣看來,就實現了使用原來函式名呼叫原來函式,並不改變原有函式程式碼並增加功能,是不是有種俄羅斯套娃的感覺呢

3、進階版本裝飾器

通過上面我們發現,index的問題是解決了,那麼假如,index裡面有引數呢,或者其他函式有引數,多個函式有不同引數,怎麼傳值呢,這裡其實可以引用之前的可變長引數的概念

def outer(func):
    def test(*args,**kwargs):  # 可以接收任何引數也可無引數
        start_time = time.time()
        func(*args,**kwargs)  # 將傳進來的引數打散再用
        end_time = time.time()
        print(start_time - end_time)
    return test

這樣是不是就得到了一個可以接收任何引數的裝飾器了呢,是的!

4、完整版本裝飾器

是不是以為這樣就結束了呢,其實不是,我們發現如果要修改的函式有返回值的話,我們的裝飾器沒有返回值,所以就需要將返回值也模擬出來,可以這樣做

def outer(func):
    def test(*args,**kwargs):  # 可以接收任何引數也可無引數
        start_time = time.time()
        res = func(*args,**kwargs)  # 將func的返回值賦值給res
        end_time = time.time()
        print(start_time - end_time)
        return res  # 返回res,就是返回原函式的返回值
    return test

這樣就如假包換了

其實,上面的過程主要是為了幫助我們理解裝飾器的概念和過程,其實我們完全可以套用模板

5、裝飾器模板

def outer(func):
    def inner(*args, **kwargs):
        '''被裝飾函式前需要增加的程式碼'''
        res = func(*args, **kwargs)
        '''被裝飾函式後需要增加的程式碼'''
        return res
    return inner

index = outer(index)
index()  # 呼叫index

6、裝飾器語法糖

@+裝飾器函式

def outer(func):
    def inner(*args, **kwargs):
        '''被裝飾函式前需要增加的程式碼'''
        res = func(*args, **kwargs)
        '''被裝飾函式後需要增加的程式碼'''
        return res
    return inner

@oueter  # 語法糖,跟在一個函式前,是對這個函式的裝飾,這句話等價於:index = outer(index)
def index():
    pass

所以語法糖就是我們之前給原函式做的一次賦值,只不過簡化了寫法

語法糖:
1.語法糖@函式名,寫在被裝飾函式的上方
2.語法糖會將下面緊挨著的函式名作為引數傳入裝飾器

三、裝飾器修復技術

本質是為了讓裝飾器做到跟原函式完全一樣,甚至指向的記憶體地址都一樣,寫法如下:

from functools import wraps  # 固定搭配,寫在這裡!!!
def outer(func):
    @wraps(func)  # 固定搭配,寫在這裡!!!
    def inner(*args, **kwargs):
        '''被裝飾函式前需要增加的程式碼'''
        res = func(*args, **kwargs)
        '''被裝飾函式後需要增加的程式碼'''
        return res
    return inner

@outer
def index():
    pass

四、作業

練習:編寫一個使用者認證裝飾器

1.基本要求
執行每個函式的時候必須先校驗身份 eg: jason 123

2.拔高練習(有點難度)
執行被裝飾的函式 只要有一次認證成功 那麼後續的校驗都通過
register login transfer withdraw

提示:全域性變數 記錄當前使用者是否認證

'''
練習:編寫一個使用者認證裝飾器

1.基本要求
  	執行每個函式的時候必須先校驗身份 eg: jason 123

2.拔高練習(有點難度)
  	執行被裝飾的函式 只要有一次認證成功 那麼後續的校驗都通過
    	register login transfer withdraw

提示:全域性變數 記錄當前使用者是否認證
'''


# 定義校驗管理員身份的裝飾器
a = 0
from functools import wraps
def account(func):
    @wraps(func)
    def check_account(*args, **kwargs):
        global a
        while a == 0:
            username = input('Input username>>>:').strip()
            password = input('Input password>>>:').strip()
            if username == 'jason' and password == '123':
                print('Administrator login!')
                a = 1
            else:
                print('Sorry,error message!')
        res = func(*args, **kwargs)
        return res
    return check_account


b = {}
@account
def register():
    regist_name = input('Input account>>>:')
    if regist_name not in b:
        regist_pwd = input('Input password>>>:')
        b[regist_name] = regist_pwd
        print('Regist success!')
    else:
        print('使用者名稱已存在!')

@account
def login():
    login_name = input('Input account>>>:')
    login_pwd = input('Input password>>>:')
    if login_name in b and login_pwd == b[login_name]:
        print('Login success!')
    else:
        print('Login failed')

@account
def delete():
    del_name = input('Input account>>>:')
    if del_name in b:
        del b[del_name]
        print('刪除成功!')
    else:
        print('Account not found!')

@account
def check():
    for i in b:
        print('''
        Account name:{0}
        password:{1}
        '''.format(i, b[i]))

@account
def exit():
    global tag
    tag = False


@account
def empty():
    pass


empty()
while True:
    print('''
        ----------------------
        1.註冊
        2.登入
        3.刪除
        4.檢視
        5.退出
        ----------------------
        ''')
    choice = input('輸入功能編號>>>:')
    if choice == '1':
        register()
    elif choice == '2':
        login()
        break
    elif choice == '3':
        delete()
    elif choice == '4':
        check()
    elif choice == '5':
        exit()
    else:
        print('指令錯誤!!!')