分享幾個比較重要的知識點,web框架中大量的運用了,所以我給摘出來啦!
- 函式的巢狀
- 閉包
- 裝飾器
1.函式巢狀
python中以函式為作用域,在作用域中定義的相關資料只能被當前作用域或子域使用
name = "隔壁老王"
print(name )
def func():
print(name )
func()
1.函式在作用域中
實際上,函式也是定義在作用域中的資料,在執行函式時,同樣遵循:優先在自己的作用域中尋找,沒有則在上一級尋找
# 1. 在全域性作用域定義了函式func
def func():
print("你好")
# 2.在全域性作用域找到func函式並執行。
func()
# 3.在全域性作用域中定義了send函式
def send():
print("開始")
# 優先在當前函式作用域找func函式,沒有則向上級作用域中尋找。
func()
print("結束")
# 4.在全域性作用域執行send函式
send()
上兩個例項,更容易理解
def func():
print("你好")
func()
def send():
print("開始")
func()
print("結束")
send()
def func():
print(666)
func()
def func():
print("你好")
func() # 你好
def send():
print("開始")
func()
print("結束")
def func(): # 會覆蓋原來的func()函式,因為這個函式名和剛開始的函式名重名了
print(666)
func() # 666
send()
# 開始
# 666
# 結束
1.2函式定義的位置
上述示例中的函式定義在全域性作用域中,其實函式也可以定義在區域性在作用域中,這樣函式被區域性作用域和其子作用域中呼叫(函式的巢狀)。
def func():
print("111")
def handler():
print("222")
def inner():
print("333")
inner()
func()
print("444")
handler()
只要理解資料定義時所存在的作用域,並根據從上到下程式碼執行過程進行分析,再怎麼巢狀都可以搞定。
現在的你可能有疑問:為什麼要這麼巢狀定義?把函式都定義在全域性不好嗎?
其實,大多數情況下我們都會將函式定義在全域性,不會巢狀著定義函式。不過,當我們定義一個函式去實現某功能,想要將內部功能拆分成N個函式,又擔心這個N個函式放在全域性會與其他函式名衝突時(尤其多人協同開發)可以選擇使用函式的巢狀。
def f1():
pass
def f2():
pass
def func():
f1()
f2()
def func():
def f1():
pass
def f2():
pass
f1()
f2()
1.3 巢狀引發的作用域問題
基於記憶體和執行過程分析作用域
name = "eric"
def run():
name = "job"
def inner():
print(name)
inner()
run()
執行函式,內部會生成一個呼叫棧,理解成會建立一個作用域
name = "eric"
def run():
name = "job"
def inner():
print(name)
return inner
v1 = run()
v1()
v2 = run()
v2()
三句話搞定作用域:
- 優先在自己的作用域找,自己沒有就去上級作用域。
- 在作用域中尋找值時,要確保此次此刻值是什麼。
- 分析函式的執行,並確定函式
作用域鏈
。(函式巢狀)
2.閉包
閉包 :基於函式的巢狀,可以將資料封裝到一個包中,以後再去呼叫
應用場景:
- 1.封裝到一個包中,供多個函式使用
- 2.封裝到一個個獨立的包中,供自己呼叫
閉包應用場景1:封裝資料防止汙染全域性。(有的函式不需要,只給需要的函式提供)
name = "eric"
def f1():
print(name, age)
def f2():
print(name, age)
def f3():
print(name, age)
def f4():
pass
def func(age):
name = "eric"
def f1():
print(name, age)
def f2():
print(name, age)
def f3():
print(name, age)
f1()
f2()
f3()
func(123)
閉包應用場景2:封裝資料封到一個包裡,使用時在取。
def task(arg):
def inner():
print(arg)
return inner
v1 = task(11) #建立一塊自己的作用域
v2 = task(22)
v3 = task(33)
v1()
v2()
v3()
不多說,直接上案例
""" 基於多執行緒去下載影片 """
from concurrent.futures.thread import ThreadPoolExecutor
import requests
def download_video(url):
res = requests.get(
url=url,
headers={
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 FS"
}
)
return res.content
def outer(file_name):
def write_file(response): # 上面的res.content會自動傳遞給response
content = response.result() # 要拿取到裡面的值必須要【.result】
with open(file_name, mode='wb') as file_object:
file_object.write(content)
return write_file
POOL = ThreadPoolExecutor(10) # 建立執行緒池
video_dict = [
("東北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
("卡特扣籃.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
]
for item in video_dict:
future = POOL.submit(download_video, url=item[1]) # 執行download_video方法,並傳遞引數url
# outer(item[0]) 這裡涉及到閉包的知識點
future.add_done_callback(outer(item[0])) # 上面執行完之後立刻執行括號中的方法【write_file函式】
POOL.shutdown()
3.裝飾器
實現原理:基於@語法和閉包,將原函式封裝到內部,並將函式賦值成新的函式【內層函式】,執行函式時在內層函式中執行閉包中封裝的原函式
實現效果:可以在不改變原函式內部程式碼 和 呼叫方式的前提下,實現在函式執行和執行擴充套件功能。
適用場景:多個函式系統統一·在 執行前後自定義一些功能。
先提個小需求,在不改變原始碼之前,實現一個函式執行之前和執行之後分別輸入"before"和"after"
對於剛剛入門的我,會這麼寫
def func():
print("before")
print("我是func函式")
value = [11, 22, 33]
print("before")
return value
result = func()
對於我來說,我會這樣做
def outer(origin):
def inner():
print("before")
res = origin()
print("after")
return inner
def func():
print("我是func函式")
value = [11, 22, 33, 44]
return value
res = outer(func)
res()
如果涉及多個地方要同時在操作前和操作後進行分別輸出
"""
由於程式碼是從上往下執行的,那麼裝飾器那裡 eg: func1 = outer(func1)
此時,func1已經是inner函式,那麼此時func1()是執行的inner函式,
"""
def outer(origin):
def inner():
print("before")
res = origin()
print("after")
return res
return inner
@outer # func1 = outer(func1)
def func1():
print("我是func1函式")
value = [11, 22, 33]
return value
@outer # func2 = outer(func2)
def func2():
print("我是func2函式")
value = [11, 22, 33]
return value
@outer # func3 = outer(func3)
def func3():
print("我是func3函式")
value = [11, 22, 33]
return value
func1()
func2()
func3()
下面,是我對裝飾器進行了最佳化【函式帶引數】
def outer(origin):
def inner(*args,**kwargs):
print("before")
res = origin(*args,**kwargs)
print("after")
return res
return inner
@outer # func1 = outer(func1)
def func1(a1):
print("我是func1函式")
value = [11, 22, 33]
return value
@outer # func2 = outer(func2)
def func2(a2, a3):
print("我是func2函式")
value = [11, 22, 33]
return value
@outer # func3 = outer(func3)
def func3(a4):
print("我是func3函式")
value = [11, 22, 33]
return value
func1(1)
func2(2, a3=4)
func3(5)
會發現裝飾器實際上就是將原函式更改為其他的函式,然後再此函式中再去呼叫原函式。
好了,裝飾器示例
def outer(origin):
def inner(*args, **kwargs):
# 執行前
res = origin(*args, **kwargs)
# 執行後
return inner
@outer
def func():
pass
func()
但是,上面的裝飾器只能夠解決百分之99的問題,下面介紹一個模組functools
加與不加的區別在於原函式.__name
和原函式.__doc
# 先認識不加的
def outer(origin):
def inner(*args, **kwargs):
"""我是小淘氣"""
# 執行前
res = origin(*args, **kwargs)
return res
# 執行後
return inner
@outer
def func():
"""我是圖圖"""
value = [11, 22]
return value
print(func.__name__) # 目前func函式 -> inner函式 所以會列印inner函式的名稱【inner】
print(func.__doc__) # 那麼這麼會列印inner函式的註釋 【我是小淘氣】
# 再認識加的
import functools
def outer(origin):
@functools.wraps(origin)
def inner(*args, **kwargs):
"""我是小淘氣"""
# 執行前
res = origin(*args, **kwargs)
return res
# 執行後
return inner
@outer
def func():
"""我是圖圖"""
value = [11, 22]
return value
print(func.__name__) # 不管目前func是什麼函式,我就列印原來函式的名稱【func】
print(func.__doc__) # 那麼這麼會列印func函式的註釋 【我是圖圖】
因為其實,一般情況下不用functools也可以實現裝飾器的基本功能,但在專案開發時,不加functools會出錯(內部會讀取__name__
,且__name__
重名的話就報錯)
好了,今天就寫到這裡叭,重新認識一下函式的高階玩法!