您好,登錄后才能下訂單哦!
維基百科中關(guān)于閉包的概念:
在一些語言中,在函數(shù)中可以(嵌套)定義另一個函數(shù)時,如果內(nèi)部的函數(shù)引用了外部的函數(shù)的變量,則可能產(chǎn)生閉包。閉包可以用來在一個函數(shù)與一組 “私有” 變量之間創(chuàng)建關(guān)聯(lián)關(guān)系。在給定函數(shù)被多次調(diào)用的過程中,這些私有變量能夠保持其持久性。
對上面這段話總結(jié)一下,即python中的閉包需要滿足3個條件:
1) 內(nèi)嵌函數(shù),即函數(shù)里定義了函數(shù) —— 這對應(yīng)函數(shù)之間的嵌套
2) 內(nèi)嵌函數(shù)必須引用定義在外部函數(shù)里中的變量(不是全局作用域中的引用)—— 內(nèi)部函數(shù)引用外部變量
3) 外部函數(shù)必須返回內(nèi)嵌函數(shù)
閉包函數(shù)示例:
def funx():
x = 1
y = 2
def funy():
print(x, y)
return funy # 返回的函數(shù)就是 閉包函數(shù)
a = funx() # 這里的 變量 a 就是閉包函數(shù)
Tip:funx 返回的函數(shù)不僅僅是函數(shù)本身,返回的函數(shù)外面還包了一層作用域關(guān)系~
所有的閉包函數(shù)都有這個屬性:__closure__(若沒有就不是閉包函數(shù),這是閉包函數(shù)的特點),a.__closure__為元組,每個元素包含了閉包外面的那層作用域中的一個變量的值,a.__closure__[0].cellcontents 和 a.__closure_\[1].cell_contents 分別引用作用域中 x 和 y 變量
print(type(a.__closure__)) # <class 'tuple'>
print(a.__closure__)
# (<cell at 0x000001CA6EAA64F8: int object at 0x000000006F036DE0>, <cell at 0x000001CA6EAA6528: int object at 0x000000006F036E00>)
print(a.__closure__[0].cell_contents) # 1
print(a.__closure__[1].cell_contents) # 2
閉包函數(shù)不光光是函數(shù),還帶了一層作用域;在調(diào)用外部函數(shù)時,可以通過參數(shù)傳遞不同的值,使得返回的閉包函數(shù)的作用域中所帶的變量各不相同。上面示例中的 x,y 也可以通過參數(shù)傳入:
def funx(x,y):
def funy():
print(x, y)
return funy # 返回的函數(shù)就是 閉包函數(shù)
a = funx(1,2) # 這里的 變量 a 就是閉包函數(shù)
所以可以總結(jié)一下,閉包的特點有2個:
1)自帶作用域
2)延遲計算
個人總結(jié)了python的閉包大致有如下2個應(yīng)用:
(1)通過自帶的作用域保留狀態(tài)
def foo(sum = 0):
def add(x):
nonlocal sum # nonlocal 指定這里使用的 sum 為外部函數(shù)中的 sum 變量
sum = sum + x
print('sum: ' + str(sum))
return add
g = foo(sum = 10)
g(4) # sum: 14
g(5) # sum: 19
g(6) # sum: 25
如上示例中,每次調(diào)用add函數(shù)(g())都會將所傳遞的參數(shù)與外部函數(shù)中的 sum 變量相加,并打印,參數(shù)的總和會保留在閉包函數(shù)自帶作用域的 sum 變量中。在獲取閉包函數(shù)時,還可以指定sum的初始大小~
(2)根據(jù)自帶作用域的局部變量來得到不同的結(jié)果
使用自帶作用域存儲文件名,每次返回的閉包函數(shù)僅用于 針對一個文件、不同關(guān)鍵字的過濾
import os, sys
def foo(filename):
def find_line(parse_str):
# 判斷文件是否存在
if not os.path.exists(filename):
print('file not exist~')
sys.exit(1)
with open(file=filename, mode='r', encoding='utf-8') as f:
for line in f:
if line.find(parse_str) != -1:
print(line, end='')
return find_line
log_1 = foo(filename=r'F:\tmp\abc.txt') # 專用于對文件 'F:\tmp\abc.txt' 的過濾
log_1('[ERROR]')
log_1('[INFO]')
log_2 = foo(filename=r'F:\tmp\abc.txt') # 專用于對文件 'F:\tmp\abc.txt' 的過濾
log_2('[ERROR]')
log_2('[INFO]')
開放封閉原則:對擴展是開放的,對修改是封閉的,即源代碼不能修改,留出擴展的可能性~
在維護過程中,很多時候需要對原有的功能(例如函數(shù))進行擴展,但是直接修改某個函數(shù)的源代碼存在一定風險,理想的狀況是在不修改源碼的情況下對函數(shù)的原有功能進行擴展~
例如現(xiàn)在需要對如下 index 函數(shù)進行擴展,計算這個函數(shù)的執(zhí)行時間~
import random, time
def index():
time.sleep(random.randrange(1,5))
print('welcome to index page')
def foo():
start_time = time.time()
index()
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
# 調(diào)用 index 函數(shù)
foo() # index()
這樣的話,源代碼沒有發(fā)生改變,新功能也添加了,但是調(diào)用方式發(fā)生了改變,原本調(diào)用 index(),現(xiàn)在變成了 foo() ~,且若要為很多個函數(shù)添加相同的功能,只能一個一個的添加
現(xiàn)改用閉包函數(shù),可為 多個函數(shù)添加相同的功能:
import random, time
def timmer(func):
def warpper():
start_time = time.time()
func()
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
return warpper
def index():
time.sleep(random.randrange(1,5))
print('welcome to index page')
def error():
time.sleep(random.randrange(2, 10))
print('welcome to error page')
index = timmer(index) # 原本直接調(diào)用index(),現(xiàn)在需要添加這么一行
index()
error = timmer(error)
error()
這樣還是存在缺陷,就是每次執(zhí)行前都需要 生成閉包函數(shù)(index = timmer(index))。那如何解決呢?就是使用裝飾器~
裝飾器的語法,在被裝飾對象的正上方 添加 '@裝飾器名字';將正下方的函數(shù)名當做一個參數(shù)傳遞給裝飾器,并將返回值重新賦值給函數(shù)名~
上面的示例通過裝飾器實現(xiàn):
import random, time
def timmer(func):
def warpper():
start_time = time.time()
func()
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
return warpper
@timmer # 等效于 index = timmer(index)
def index():
time.sleep(random.randrange(1,5))
print('welcome to index page')
@timmer
def error():
time.sleep(random.randrange(2, 10))
print('welcome to error page')
index() # 調(diào)用的是warpper()
error()
這樣就滿足了開放封閉原則~
裝飾器可以添加多個,執(zhí)行順序是 從下往上執(zhí)行,如下示例中是 先添加auth,再執(zhí)行timmer ~,即 index 函數(shù)先由 auth 進行封裝,而后在這個基礎(chǔ)之上再由 timmer 進行封裝~
def timmer(func):
def warpper():
print('timmer_before_codes')
func() # 執(zhí)行時這里的 func 就是 deco (即 auth(index))
print('timmer_after_codes')
return warpper
def auth(func):
def deco():
print('auth_before_codes')
func() # 執(zhí)行時這里是原始的index()
print('auth_after_codes')
return deco
@timmer
@auth
def index():
print('welcome to index page')
index() # 調(diào)用 index()
調(diào)用index后輸出結(jié)果如下:
timmer_before_codes
auth_before_codes
index page
auth_after_codes
timmer_after_codes
很簡單,就是將參數(shù)傳遞到被裝飾的函數(shù)當中~
def auth(func):
def warpper(user):
print('before_user_login')
func(user)
print('after_user_login')
return warpper
@auth
def login(user):
print('%s login success' % user)
login('kitty')
但是這個時候無參函數(shù)無法再使用這個裝飾器進行裝飾~,*agrs, **kwargs,可以使用可變長參數(shù)解決這個問題:
def auth(func):
def warpper(*agrs, **kwargs):
print('before_user_login')
func(*agrs, **kwargs)
print('after_user_login')
return warpper
@auth
def login(user):
print('%s login success' % user)
@auth
def index():
print('welcome to index page')
login('kitty')
index()
若原始函數(shù)有返回值,在內(nèi)部函數(shù)中 調(diào)用原始函數(shù),獲取返回值,并通過內(nèi)部函數(shù)進行返回即可~
def timmer(func):
def warpper(*agrs, **kwargs):
start_time = time.time()
res = func(*agrs, **kwargs)
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
return res
return warpper
原始函數(shù)被裝飾以后,原有的一些屬性會被 裝飾后的函數(shù)所替代,例如文檔字符串~
def timmer(func):
def warpper(*agrs, **kwargs):
'warpper function' # 文檔字符串
start_time = time.time()
res = func(*agrs, **kwargs)
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
return res
return warpper
@timmer
def index():
'index function' # 文檔字符串
print('welcome to index page')
print(index.__doc__)
輸出結(jié)果:
warpper function
原本想獲取index函數(shù)的說明信息,而返回的卻是 warpper 函數(shù)的。這里可以使用 @wraps(func) ,用以保留原函數(shù)自己的一些原始信息;若函數(shù)已被裝飾,又想調(diào)用原始的函數(shù),可以在調(diào)用函數(shù)時使用函數(shù)的 wrapped 屬性 就能夠使用原始的函數(shù),而不是被裝飾后的函數(shù)~
def timmer(func):
@wraps(func)
def warpper(*agrs, **kwargs):
'warpper function' # 文檔字符串
start_time = time.time()
res = func(*agrs, **kwargs)
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))
return res
return warpper
@timmer
def index():
'index function' # 文檔字符串
print('welcome to index page')
print(index.__doc__)
index.__wrapped__() # 調(diào)用原始函數(shù)
輸出結(jié)果:
index function
welcome to index page
之前用到的都是無參裝飾器,有參裝飾器,簡單的說就是在原有的裝飾器外面再套上一層帶參數(shù)的函數(shù)~
還是這個函數(shù),現(xiàn)在除了添加計時功能外,還需要添加debug功能,debug是否啟用通過參數(shù)來實現(xiàn)~
def index():
time.sleep(random.randrange(1,5))
print('welcome to index page')
添加有參裝飾器:
import time, random
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def timmer(is_debug):
def decorator(func):
def wrapper(*args, **kwargs):
if is_debug:
begin = time.time()
res = func(*args, **kwargs)
logging.debug( "[" + func.__name__ + "] --> " + str(time.time() - begin) )
else:
res = func(*args, **kwargs)
return res
return wrapper
return decorator
@timmer(is_debug = True)
def index():
time.sleep(random.randrange(1, 5))
print('welcome to index page')
index()
index函數(shù)上方的 @timmer(is_debug = True) 相當于 index = timmer(is_debug=True)(index),開啟debug后輸出結(jié)果如下:
welcome to index page
DEBUG:root:[index] --> 3.000962018966675
@timmer(is_debug = False),關(guān)閉 debug 后,函數(shù)的輸出與裝飾前一致:
welcome to index page
.................^_^
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。