python中的閉包和裝飾器
什麽是閉包
維基百科中關於閉包的概念:
在一些語言中,在函數中可以(嵌套)定義另一個函數時,如果內部的函數引用了外部的函數的變量,則可能產生閉包。閉包可以用來在一個函數與一組 “私有” 變量之間創建關聯關系。在給定函數被多次調用的過程中,這些私有變量能夠保持其持久性。
對上面這段話總結一下,即python中的閉包需要滿足3個條件:
1) 內嵌函數,即函數裏定義了函數 —— 這對應函數之間的嵌套
2) 內嵌函數必須引用定義在外部函數裏中的變量(不是全局作用域中的引用)—— 內部函數引用外部變量
3) 外部函數必須返回內嵌函數
閉包函數示例:
def funx(): x = 1 y = 2 def funy(): print(x, y) return funy # 返回的函數就是 閉包函數 a = funx() # 這裏的 變量 a 就是閉包函數
Tip:funx 返回的函數不僅僅是函數本身,返回的函數外面還包了一層作用域關系~
所有的閉包函數都有這個屬性:closure(若沒有就不是閉包函數,這是閉包函數的特點),a.closure為元組,每個元素包含了閉包外面的那層作用域中的一個變量的值,a.closure[0].cell_contents 和 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
閉包的應用
閉包函數不光光是函數,還帶了一層作用域;在調用外部函數時,可以通過參數傳遞不同的值,使得返回的閉包函數的作用域中所帶的變量各不相同。上面示例中的 x,y 也可以通過參數傳入:
def funx(x,y):
def funy():
print(x, y)
return funy # 返回的函數就是 閉包函數
a = funx() # 這裏的 變量 a 就是閉包函數
所以可以總結一下,閉包的特點有2個:
1)自帶作用域
2)延遲計算
個人總結了python的閉包大致有如下2個應用:
(1)通過自帶的作用域保留狀態
def foo(sum = 0): def add(x): nonlocal sum # nonlocal 指定這裏使用的 sum 為外部函數中的 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
如上示例中,每次調用add函數(g())都會將所傳遞的參數與外部函數中的 sum 變量相加,並打印,參數的總和會保留在閉包函數自帶作用域的 sum 變量中。在獲取閉包函數時,還可以指定sum的初始大小~
(2)根據自帶作用域的局部變量來得到不同的結果
使用自帶作用域存儲文件名,每次返回的閉包函數僅用於 針對一個文件、不同關鍵字的過濾
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]‘)
開放封閉原則
開放封閉原則:對擴展是開放的,對修改是封閉的,即源代碼不能修改,留出擴展的可能性~
在維護過程中,很多時候需要對原有的功能(例如函數)進行擴展,但是直接修改某個函數的源代碼存在一定風險,理想的狀況是在不修改源碼的情況下對函數的原有功能進行擴展~
例如現在需要對如下 index 函數進行擴展,計算這個函數的執行時間~
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))
# 調用 index 函數
foo() # index()
這樣的話,源代碼沒有發生改變,新功能也添加了,但是調用方式發生了改變,原本調用 index(),現在變成了 foo() ~,且若要為很多個函數添加相同的功能,只能一個一個的添加
現改用閉包函數,可為 多個函數添加相同的功能:
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) # 原本直接調用index(),現在需要添加這麽一行
index()
error = timmer(error)
error()
這樣還是存在缺陷,就是每次執行前都需要 生成閉包函數(index = timmer(index))。那如何解決呢?就是使用裝飾器~
裝飾器
裝飾器語法
裝飾器的語法,在被裝飾對象的正上方 添加 ‘@裝飾器名字‘;將正下方的函數名當做一個參數傳遞給裝飾器,並將返回值重新賦值給函數名~
上面的示例通過裝飾器實現:
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() # 調用的是warpper()
error()
這樣就滿足了開放封閉原則~
多個裝飾器的使用
裝飾器可以添加多個,執行順序是 從下往上執行,如下示例中是 先添加auth,再執行timmer ~,即 index 函數先由 auth 進行封裝,而後在這個基礎之上再由 timmer 進行封裝~
def timmer(func):
def warpper():
print(‘timmer_before_codes‘)
func() # 執行時這裏的 func 就是 deco (即 auth(index))
print(‘timmer_after_codes‘)
return warpper
def auth(func):
def deco():
print(‘auth_before_codes‘)
func() # 執行時這裏是原始的index()
print(‘auth_after_codes‘)
return deco
@timmer
@auth
def index():
print(‘welcome to index page‘)
index() # 調用 index()
調用index後輸出結果如下:
timmer_before_codes
auth_before_codes
index page
auth_after_codes
timmer_after_codes
原始函數有參數的場景
很簡單,就是將參數傳遞到被裝飾的函數當中~
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‘)
但是這個時候無參函數無法再使用這個裝飾器進行裝飾~,*agrs, **kwargs,可以使用可變長參數解決這個問題:
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()
原始函數有返回值的場景
若原始函數有返回值,在內部函數中 調用原始函數,獲取返回值,並通過內部函數進行返回即可~
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
wraps 的常用功能
原始函數被裝飾以後,原有的一些屬性會被 裝飾後的函數所替代,例如文檔字符串~
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__)
輸出結果:
warpper function
原本想獲取index函數的說明信息,而返回的卻是 warpper 函數的。這裏可以使用 @wraps(func) ,用以保留原函數自己的一些原始信息;若函數已被裝飾,又想調用原始的函數,可以在調用函數時使用函數的 wrapped 屬性 就能夠使用原始的函數,而不是被裝飾後的函數~
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__() # 調用原始函數
輸出結果:
index function
welcome to index page
有參裝飾器
之前用到的都是無參裝飾器,有參裝飾器,簡單的說就是在原有的裝飾器外面再套上一層帶參數的函數~
還是這個函數,現在除了添加計時功能外,還需要添加debug功能,debug是否啟用通過參數來實現~
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函數上方的 @timmer(is_debug = True) 相當於 index = timmer(is_debug=True)(index),開啟debug後輸出結果如下:
welcome to index page
DEBUG:root:[index] --> 3.000962018966675
@timmer(is_debug = False),關閉 debug 後,函數的輸出與裝飾前一致:
welcome to index page
.................^_^
python中的閉包和裝飾器