來源:
【參考:Python必知必會 - 閉包和裝飾器】
【參考:Python的閉包和裝飾器,我是這樣理解的_哔哩哔哩_bilibili】
在原有基礎上添加自己的思考過程
代碼可視化網站:【參考:Python Tutor: Learn Python, JavaScript, C, C++, and Java by visualizing code】
LEGB查找原則(局部,嵌套,全局,內置作用域)
變量的作用域只與定義的位置有關,與函數調用位置無關
Local、Enclosing function locals、Global、Built-in
當引用一個變量時,Python按以下順序依次進行查找:
第一個能夠完成查找的就算成功,就會停止往下查找
s = "G" # 3. (G) 全局作用域
def computer(value=20):
# 嵌套作用域內的變量:value, prize函數, s
def prize():
print("s:", s) # 1.(L) 本地變量
print("inner:", value)
s = "E" # 2.(E) 上一層結構中的def或lambda的本地作用域
prize() # 變量的作用域只與定義的位置有關,與函數調用位置無關
computer(88)
""" s: E inner: 88 """
word = "global"
def outer(word="TAT"):
# 作用域:word(參數), inner函數
print(word) # L
def inner():
print(word) # E
word += "enclose"
inner()
outer()
print(word) # G
""" TAT TATenclose global """
【參考:python閉包的作用_whgiser的博客-CSDN博客】
維基百科:在一些語言中,在函數中可以(嵌套)定義另一個函數時,如果內部的函數引用了外部的函數的變量,則可能產生閉包。閉包可以用來在一個函數與一組“私有”變量之間創建關聯關系。在給定函數被多次調用的過程中,這些私有變量能夠保持其持久性。
未使用閉包
# 為什麼我們要引入閉包?
func_list = []
for k in range(1, 5):
def func(x):
return x * k
func_list.append(func) # 只存入函數名,參數的值沒有存入
# 上面運行完後 k=4
for func in func_list: # 希望是 1x,2x,3x,4x
print(func(3)) # 希望是 3, 6, 9, 12
# 結果為 3*4=12
""" 12 12 12 12 """
引入閉包
# 使用閉包解決上述問題
def enclose(k):
# 嵌套作用域:參數k,函數func
def func(x):
return x * k
return func
func_list = []
for k in range(1, 5):
func_list.append(enclose(k)) # 閉包能記住當時的作用域 這裡存入了函數和參數值
for func in func_list: # 1x,2x,3x,4x
print(func(3)) # 3, 6, 9, 12
""" 3 6 9 12 """
備注:閉包還是記不住全局作用域內的變量的
# ============================================
# == 閉包可以記住嵌套作用域(即上一層作用域)
# ============================================
T = 1
def enclose(day):
# 嵌套作用域 變量day,函數func
day = day + T
def func(x):
print("day: ", day) # 1. 嵌套作用域內的變量day可以記住
print(x + T) # 2. 全局變量T仍然無法記住,只有在調用func函數時才能使用T的值
return func
f = enclose(day=7)
# 這個函數返回一個變量名func並賦值給f,並且func記住了當時enclose嵌套作用域內的變量day
""" 全局變量T=1 day = 7+1 =8 """
T += 1
g = enclose(day=7)
""" 全局變量T=2 day= 7+2 =9 """
f(x=1)
""" 全局變量T=2 func(): x+T = 1+2 """
g(x=1)
""" 全局變量T=2 func(): x+T = 1+2 """
輸出:
""" day: 8 3 day: 9 3 """
裝飾器就是個閉包(只不過外層def傳入的參數是個函數變量),只不過多了一個語法糖
裝飾器讓你在一個函數的前後去執行代碼。
import time
# 裝飾器函數
def outer(origin):
# origin是一個函數名
def inner():
t1 = time.time()
origin()
t2 = time.time()
print(f"finish func: {
origin.__name__}, time cost: {
t2 - t1:.6f}(sec)")
return inner
# --------1----------
def myfunc1():
for i in range(100000):
pass
print("我是myfunc1函數")
func = outer(myfunc1)
""" 相當於 origin = myfunc1 func = inner """
func()
""" 相當於調用inner() 然後會執行origin()即myfunc1() """
# --------2---------- 裝飾器
# 語法糖 使用裝飾器
@outer # myfunc2= outer(myfunc2) 在def之後執行
def myfunc2():
for i in range(100000):
pass
print("我是myfunc2函數")
myfunc2()
""" 我是myfunc1函數 finish func: myfunc1, time cost: 0.001995(sec) 我是myfunc2函數 finish func: myfunc2, time cost: 0.001994(sec) """
典型的裝飾器寫法
import time
def outer(origin):
def inner(*args, **kwargs):
t1 = time.time()
origin(*args, **kwargs) # 需要接收參數
t2 = time.time()
print(f"finish func: {
origin.__name__}, time cost: {
t2 - t1:.6f}(sec)")
return inner
@outer # func1_new = outer(func1) 在def之後執行
def func1(a1):
print("我是func1函數")
print(a1)
value = 11
return value
""" 相當於 origin=func1 func1_new =inner """
func1(-1) # func1_new
""" 相當於調用inner(-1) 然後會執行origin(-1)即func1(-1) """
@outer
def func2(b1,b2):
print("我是func2函數")
print(b1,b2)
value = 22
return value
@outer
def func3(s="special"):
print("我是func3函數")
value = 33
return value
func2(8, 9)
func3(s="time")
我是func1函數
-1
finish func: func1, time cost: 0.000000(sec)
我是func2函數
8 9
finish func: func2, time cost: 0.000000(sec)
我是func3函數
finish func: func3, time cost: 0.000000(sec)
import logging
logging.basicConfig()
# 聲明一個 Logger 對象
logger = logging.getLogger(__name__)
# 設置忽略級別
logger.setLevel(level=logging.INFO)
# 嵌套的裝飾器
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "info":
logger.info("%s is running" % func.__name__)
func(*args)
return wrapper
return decorator
def foo(name='foo'):
print("i am %s" % name)
foo = use_logging(level='warn')(foo)
foo()
# ============= 等價 ==============
# python語法糖
# foo = use_logging(level='warn')(foo)
# 可以看出@後面的內容就是上行代碼去掉左邊的foo和右邊的(foo)剩下的內容
@use_logging(level='warn')
def foo(name='foo'):
print("i am %s" % name)
""" level='warn' func=foo foo_new = decorator """
foo() # foo_new
""" 相當於調用decorator() 然後會執行wrapper(),wrapper裡面會執行func()即foo() """
i am foo
i am foo
class myDecorator(object):
def __init__(self, f):
print("inside myDecorator.__init__()")
f() # Prove that function definition has completed
self.g = f # 將函數變量f設置為類屬性g
def __call__(self):
print("inside myDecorator.__call__()")
self.g()
def aFunction():
print("inside aFunction()")
aFunction = myDecorator(aFunction) # aFunction是一個類實例化後的對象
print("=== Finished decorating aFunction() ===")
aFunction() # 會調用call
print('------------------------------')
@myDecorator
def bFunction():
print("inside bFunction()")
bFunction()
inside myDecorator.__init__()
inside aFunction()
=== Finished decorating aFunction() ===
inside myDecorator.__call__()
inside aFunction()
------------------------------
inside myDecorator.__init__()
inside bFunction()
inside myDecorator.__call__()
inside bFunction()
class Decorator:
def __init__(self, arg1, arg2):
print('執行類Decorator的__init__()方法')
self.arg1 = arg1
self.arg2 = arg2
def __call__(self, f):
print('執行類Decorator的__call__()方法')
# wrap = xxx
def wrap(*args):
print('執行wrap()')
print('裝飾器參數:', self.arg1, self.arg2)
print('執行' + f.__name__ + '()')
f(*args) # 執行顧客的功能
print(f.__name__ + '()執行完畢')
return wrap
@Decorator('Hello', 'World') # 2. example = Decorator('Hello', 'World')(example)
def example(a1, a2, a3): # 1. example = xxx
print('傳入example()的參數:', a1, a2, a3)
print('裝飾完畢')
print('准備調用example()')
example('Wish', 'Happy', 'EveryDay') # 3.
print('測試代碼執行完畢')
執行類Decorator的__init__()方法
執行類Decorator的__call__()方法
裝飾完畢
准備調用example()
執行wrap()
裝飾器參數: Hello World
執行example()
傳入example()的參數: Wish Happy EveryDay
example()執行完畢
測試代碼執行完畢
未使用wraps
from functools import wraps
def my_decorator(func):
# @wraps(func) # 防止被裝飾函數example的函數名稱和docstring被修改
def wrapper(*args, **kwargs):
'''decorator'''
print('Calling decorated function...')
func(*args, **kwargs)
return wrapper
@my_decorator
def example(): # 被裝飾函數
"""Docstring"""
print('Called example function')
example()
print(example.__name__, example.__doc__)
""" Calling decorated function... Called example function wrapper decorator """
使用wraps
from functools import wraps
def my_decorator(func):
@wraps(func) # 防止被裝飾函數example的函數名稱和docstring被修改
def wrapper(*args, **kwargs):
'''decorator'''
print('Calling decorated function...')
func(*args, **kwargs)
return wrapper
@my_decorator
def example(): # 被裝飾函數
"""Docstring"""
print('Called example function')
example()
print(example.__name__, example.__doc__)
""" Calling decorated function... Called example function example Docstring """
from functools import wraps
def outer(origin):
@wraps(origin)
def inner(*args, **kwargs):
print("before ...") # 執行前
sum = origin(*args, **kwargs) # 調用原來的func函數
print("after ...") # 執行後
return sum
return inner
@outer # func = outer(func)
def func(x, y):
print(f"x: {
x}, y: {
y}")
return x + y
res = func(3,7)
print(res)
""" before ... x: 3, y: 7 after ... 10 """