Python之闭包与装饰器
閉包
由于閉包這個概念比較難以理解,尤其是初學者來說,相對難以掌握,所以我們通過示例去理解學習閉包。
給大家提個需求,然后用函數去實現:完成一個計算不斷增加的系列值的平均值的需求。
例如:整個歷史中的某個商品的平均收盤價。什么叫平局收盤價呢?就是從這個商品一出現開始,每天記錄當天價格,然后計算他的平均值:平均值要考慮直至目前為止所有的價格。
比如大眾推出了一款新車:小白轎車。
第一天價格為:100000元,平均收盤價:100000元
第二天價格為:110000元,平均收盤價:(100000 + 110000)/2 元
第三天價格為:120000元,平均收盤價:(100000 + 110000 + 120000)/3 元
series = [] def make_averager(new_value):series.append(new_value)total = sum(series)return total / len(series)print(make_averager(100000)) print(make_averager(110000)) print(make_averager(120000))從上面的例子可以看出,基本上完成了我們的要求,但是這個代碼相對來說是不安全的,因為你的這個series列表是一個全局變量,只要是全局作用域的任何地方,都可能對這個列表進行改變。
series = [] def make_averager(new_value):series.append(new_value)total = sum(series)return total / len(series)print(make_averager(100000)) print(make_averager(110000)) series.append(666) # 如果對數據進行相應改變,那么你的平均收盤價就會出現很大的問題。 print(make_averager(120000))那么怎么辦呢?有人說,你把他放在函數中不就行了,這樣不就是局部變量了么?數據不就相對安全了么?
def make_averager(new_value):series = []series.append(new_value)total = sum(series)return total / len(series)print(make_averager(100000)) # 100000.0 print(make_averager(110000)) # 110000.0 print(make_averager(120000)) # 120000.0這樣計算的結果是不正確的,那是因為執行函數,會開啟一個臨時的名稱空間,隨著函數的結束而消失,所以你每次執行函數的時候,都是重新創建這個列表,那么這怎么做呢?這種情況下,就需要用到我們講的閉包了,我們用閉包的思想改一下這個代碼。
def make_averager():series = []def averager(new_value):series.append(new_value)total = sum(series)return total/len(series)return averageravg = make_averager() print(avg(100000)) print(avg(110000)) print(avg(120000))大家仔細看一下這個代碼,我是在函數中嵌套了一個函數。那么avg 這個變量接收的實際是averager函數名,也就是其對應的內存地址,我執行了三次avg 也就是執行了三次averager這個函數。那么此時你們有什么問題?
肯定有學生就會問,那么我的make_averager這個函數只是執行了一次,為什么series這個列表沒有消失?反而還可以被調用三次呢?這個就是最關鍵的地方,也是閉包的精華所在。我給大家說一下這個原理,以圖為證:
? 上面被紅色方框框起來的區域就是閉包,被藍色圈起來的那個變量應該是make_averager()函數的局部變量,它應該是隨著make_averager()函數的執行結束之后而消失。但是他沒有,是因為此區域形成了閉包,series變量就變成了一個叫自由變量的東西,averager函數的作用域會延伸到包含自由變量series的綁定。也就是說,每次我調用avg對應的averager函數 時,都可以引用到這個自用變量series,這個就是閉包。
閉包的定義:
? 1. 閉包是嵌套在函數中的函數。
? 2. 閉包必須是內層函數對外層函數的變量(非全局變量)的引用。
如何判斷判斷閉包?舉例讓同學回答:
# 例一: def wrapper():a = 1def inner():print(a)return inner ret = wrapper()# 例二: a = 2 def wrapper():def inner():print(a)return inner ret = wrapper()# 例三:def wrapper(a,b):def inner():print(a)print(b)return inner a = 2 b = 3 ret = wrapper(a,b)以上三個例子,最難判斷的是第三個,其實第三個也是閉包,如果我們每次去研究代碼判斷其是不是閉包,有一些不科學,或者過于麻煩了,那么有一些函數的屬性是可以獲取到此函數是否擁有自由變量的,如果此函數擁有自由變量,那么就可以側面證明其是否是閉包函數了(了解):
def make_averager():series = []def averager(new_value):series.append(new_value)total = sum(series)return total/len(series)return averager avg = make_averager() # 函數名.__code__.co_freevars 查看函數的自由變量 print(avg.__code__.co_freevars) # ('series',) 當然還有一些參數,僅供了解:# 函數名.__code__.co_varnames 查看函數的局部變量 print(avg.__code__.co_varnames) # ('new_value', 'total') # 函數名.__closure__ 獲取具體的自由變量對象,也就是cell對象。 # (<cell at 0x0000020070CB7618: int object at 0x000000005CA08090>,) # cell_contents 自由變量具體的值 print(avg.__closure__[0].cell_contents) # []閉包的作用:保存局部信息不被銷毀,保證數據的安全性。
閉包的應用:
閉包的解釋:
閉包是存在嵌套函數當中的 內層對外層非全局變量的的引用 ,稱之為自由變量 它不會隨著函數的結束而消失,一直保存在內存,最終的目的是保證數據的安全
裝飾器
開放封閉原則
軟件面世時,不可能吧所有的功能都設計好,當前的未來一兩年功能給你上線,定期更新迭代.對于軟件之前寫的源代碼一般都不會修改,對函數里面的代碼以及函數的調用方式.
開放原則: 在源碼不改變的情況下,增加一些額外的功能
封閉原則: 不要改變源代碼
python中裝飾器: 完美詮釋的開放封閉原則
裝飾器 就是個函數 : 他要裝飾一個函數,在不改變原函數以及調用方式的前提下,給其增加一個額外的功能
裝飾器初識
# 1 李業,在一家xx科技有限公司工作,主管安排了一個任務, # 寫一個代碼測試懟懟哥寫的函數的執行效率。 # import time # def index(): # time.sleep(2) # print('歡迎訪問博客園首頁')# print(time.time()) # start_time = time.time() # index() # end_time = time.time() # print(f'此函數的執行效率{end_time-start_time}')# 2. 主管讓你測試小鄧,李大象,重復代碼太多。 # # def func1(): # time.sleep(2) # print('歡迎訪問日記首頁') # # # def func2(): # time.sleep(1) # print('歡迎訪問評論首頁')# start_time = time.time() # func1() # end_time = time.time() # print(f'此函數的執行效率{end_time-start_time}') # # start_time = time.time() # func2() # end_time = time.time() # print(f'此函數的執行效率{end_time-start_time}')# 3. 整合到函數中# def func1(): # time.sleep(2) # print('歡迎訪問日記首頁') # # # def func2(): # time.sleep(1) # print('歡迎訪問評論首頁')# def test_time(x): # start_time = time.time() # x() # end_time = time.time() # print(f'此函數的執行效率{end_time-start_time}')# test_time(func1) # test_time(func2)# 4. 懟懟哥這個函數在實際項目中被500執行,主管要求:在被執行此函數時, # 同時要測試一下被執行函數的效率。# def index(): # time.sleep(2) # print('歡迎訪問博客園首頁') # # # index() # def test_time(x): # start_time = time.time() # x() # end_time = time.time() # print(f'此函數的執行效率{end_time-start_time}') # # test_time(index)# 版本4的問題: 開放原則滿足了,封閉原則:不改變原函數的源碼,以及調用方式。 # 違反了封閉原則:改變了函數的調用方式。# 版本5: 不能改變原函數的調用方式(閉包):# def index(): # time.sleep(2) # print('歡迎訪問博客園首頁') # # # index() # # # def func1(): # # time.sleep(2) # # print('歡迎訪問日記首頁') # # def test_time(x): # x = index # def inner(): # start_time = time.time() # x() # end_time = time.time() # print(f'此函數的執行效率{end_time-start_time}') # return inner # # index = test_time(index) # index()# 語法糖 @加上裝飾器函數的名# def f(): # print(666) # # # f = '太白' # print(f)# def test_time(x): # x = index # def inner(): # start_time = time.time() # x() # end_time = time.time() # print(f'此函數的執行效率{end_time-start_time}') # return inner # # # # @test_time # index = test_time(index) # def index(): # time.sleep(2) # print('歡迎訪問博客園首頁')# index = test_time(index) # index()# def func1(): # time.sleep(2) # print('歡迎訪問日記首頁')# @test_time # def func2(): # time.sleep(1) # print('歡迎訪問評論首頁')# func2 = test_time(func2) # func3 = test_time(func3) # func2() '''100行代碼''' # index()'''10行代碼''' # index()'''50行代碼''' # index()# 版本6:被裝飾函數有返回值# def test_time(x): # x = index # def inner(): # start_time = time.time() # ret = x() # # print(F'ret: {ret}') # end_time = time.time() # print(f'此函數的執行效率{end_time-start_time}') # return ret # return inner # # # @test_time # index = test_time(index) # def index(): # time.sleep(0.5) # print('歡迎訪問博客園首頁') # return True # # print(index()) # inner() # 你應該是讓True返回給index()這樣才完美了,但是現在index是inner,所以你要是完全不改變原函數的使用, # 你print(index()) ---> True# 版本7: 被裝飾函數帶參數,無論加不加裝飾器,你的實參'太白金星'應該傳給形參n,。 # 但版本6不能實現傳參,index('太白金星') == inner('太白金星') # # def test_time(x): # x = index # def inner(*args,**kwargs): # # 函數的定義:* ** 聚合。 # # args = ('蘋果') # #args = (1, 3) # start_time = time.time() # ret = x(*args,**kwargs) # # 函數的執行:* ** 打散。 # # ret = x(*('蘋果')) ==x('蘋果',) # # ret = x(*(1, 3)) ==x(1,3) # # print(F'ret: {ret}') # end_time = time.time() # print(f'此函數的執行效率{end_time-start_time}') # return ret # return inner # # # # @test_time # index = test_time(index) # def index(n): # time.sleep(0.5) # print(f'歡迎{n}訪問博客園首頁') # return True # # # @test_time # index = test_time(index) # def func2(a,b): # time.sleep(0.5) # print(f'最終結果:{a+b}') # return a + b # # # print(index('蘋果')) # inner('蘋果') # print(func2(1,3)) # == inner(1,3)# def warpper(f): # def inner(*args,**kwargs): # '''被裝飾函數之前的操作''' # # print(666) # ret = f(*args,**kwargs) # '''被裝飾函數之后的操作''' # # print('執行完畢了') # return ret # return inner # # @warpper # def func(): # print(111) # # # func() # func() # func() # func() # func()# 裝飾器的應用:在不改變原函數的源碼以及調用方式前提下,為其增加額外的功能。 # 登陸認證,打印日志等。帶參數裝飾器
def wrapper(f):def inner(*args,**kwargs):if f.__name__ == 'qq':ret = f(*args,**kwargs)return retelse:return innerdef wrapper(f):def inner(*args, **kwargs):ret = f(*args, **kwargs)return retreturn innerdef wrapper_out(n,*args,sex='男',):def wrapper(f): # fdef inner(*args,**kwargs):ret = f(*args,**kwargs) # func1()return retreturn innerreturn wrapperdef func1():print('in func1') func = wrapper_out(1) # wrapper函數名 ly = func(func1) # wrapper(func1) = inner ly() # inner()def wrapper_out(n):def wrapper(f):def inner(*args,**kwargs):# if n == 'qq':# username = input('請輸入用戶名:').strip()# password = input('請輸入密碼:').strip()# with open('qq',encoding='utf-8') as f1:# for line in f1:# user,pwd = line.strip().split('|')# if username == user and password == pwd:# print('登陸成功')# ret = f(*args,**kwargs)# return ret# return False# elif n == 'tiktok':# username = input('請輸入用戶名:').strip()# password = input('請輸入密碼:').strip()# with open('tiktok', encoding='utf-8') as f1:# for line in f1:# user, pwd = line.strip().split('|')# if username == user and password == pwd:# print('登陸成功')# ret = f(*args, **kwargs)# return ret# return Falseusername = input('請輸入用戶名:').strip()password = input('請輸入密碼:').strip()with open(n,encoding='utf-8') as f1:for line in f1:user,pwd = line.strip().split('|')if username == user and password == pwd:print('登陸成功')ret = f(*args,**kwargs)return retreturn Falsereturn innerreturn wrapper """ # @wrapper_out('qq') # def qq(): # print('成功訪問qq') # qq() # 看到帶參數的裝飾器分兩步執行: ''' @wrapper_out('騰訊')1. 執行wrapper_out('騰訊') 這個函數,把相應的參數'騰訊' 傳給 n,并且得到返回值 wrapper函數名。2. 將@與wrapper結合,得到我們之前熟悉的標準版的裝飾器按照裝飾器的執行流程執行。 ''' """@wrapper_out('qq') def qq():print('成功訪問qq')@wrapper_out('tiktok') def tiktok():print('成功訪問抖音')qq() tiktok() 開發思路:增強耦合性多個裝飾器裝飾一個函數
def wrapper1(func1): # func1 = f原函數def inner1():print('wrapper1 ,before func') # 2func1()print('wrapper1 ,after func') # 4return inner1def wrapper2(func2): # func2 == inner1def inner2():print('wrapper2 ,before func') # 1func2() # inner1print('wrapper2 ,after func') # 5return inner2@wrapper2 # f = wrapper2(f) 里面的f == inner1 外面的f == inner2 @wrapper1 # f = wrapper1(f) 里面的f == func1 外面的 f == inner1 def f():print('in f') # 3f() # inner2()轉載于:https://www.cnblogs.com/Jacob-yang/p/11107496.html
總結
以上是生活随笔為你收集整理的Python之闭包与装饰器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机什么专业适合男生,内向的男生适合什
- 下一篇: 初学Python可能会遇见的小程序