帶你了解python裝飾器
1.作用域
在python中,作用域分為兩種:全局作用域和局部作用域。
全局作用域是定義在文件級別的變量,函數(shù)名。而局部作用域,則是定義函數(shù)內(nèi)部。
關于作用域,我要理解兩點:a.在全局不能訪問到局部定義的變量 b.在局部能夠訪問到全局定義的變量,但是不能修改全局定義的變量(當然有方法可以修改)
下面我們來看看下面實例:
x = 1 def funx(): x = 10 print(x) # 打印出10 funx() print(x) # 打印出1
如果局部沒有定義變量x,那么函數(shù)內(nèi)部會從內(nèi)往外開始查找x,如果沒有找到,就會報錯
x = 1
def funx():
print(x) # 打印出1
funx()
print(x) # 打印出1
x = 1
def funx():
def func1():
print(x) # 打印出1
func1()
funx()
print(x) # 打印出1
因此,關于作用域的問題,只需要記住兩點就行:全局變量能夠被文件任何地方引用,但修改只能在全局進行操作;如果局部沒有找到所需的變量,就會往外進行查找,沒有找到就會報錯。
2.高級函數(shù)
我們知道,函數(shù)名其實就是指向一段內(nèi)存空間的地址,既然是地址,那么我們可以利用這種特性來。
a函數(shù)名可以作為一個值
def delete(ps):
import os
filename = ps[-1]
delelemetns = ps[1]
with open(filename, encoding='utf-8') as f_read,\
open('tmp.txt', 'w', encoding='utf-8') as f_write:
for line in iter(f_read.readline, ''):
if line != '\n': # 處理非空行
if delelemetns in line:
line = line.replace(delelemetns,'')
f_write.write(line)
os.remove(filename)
os.rename('tmp.txt',filename)
def add(ps):
filename = ps[-1]
addelemetns = ps[1]
with open(filename, 'a', encoding='utf-8') as fp:
fp.write("\n", addelemetns)
def modify(ps):
import os
filename = ps[-1]
modify_elemetns = ps[1]
with open(filename, encoding='utf-8') as f_read, \
open('tmp.txt', 'w', encoding='utf-8') as f_write:
for line in iter(f_read.readline, ''):
if line != '\n': # 處理非空行
if modify_elemetns in line:
line = line.replace(modify_elemetns, '')
f_write.write(line)
os.remove(filename)
os.rename('tmp.txt', filename)
def search(cmd):
filename = cmd[-1]
pattern = cmd[1]
with open(filename, 'r', encoding="utf-8") as f:
for line in f:
if pattern in line:
print(line, end="")
else:
print("沒有找到")
dic_func ={'delete': delete, 'add': add, 'modify': modify, 'search': search}
while True:
inp = input("請輸入您要進行的操作:").strip()
if not inp:
continue
cmd_1 = inp.split()
cmd = cmd_1[0]
if cmd in dic_func:
dic_func[cmd](cmd_1)
else:
print("Error")
b.函數(shù)名可以作為返回值
def outer():
def inner():
pass
return inner
s = outer()
print(s)
######輸出結果為#######
<function outer.<locals>.inner at 0x000000D22D8AB8C8>
c..函數(shù)名可以作為一個參數(shù)
def index():
print("index func")
def outer(index):
s = index
s()
outer(index)
######輸出結果#########
index func
所以滿足上面兩個條件中的一個,都可以稱為高級函數(shù).
3.閉包函數(shù)
閉包函數(shù)必須滿足兩個條件:1.函數(shù)內(nèi)部定義的函數(shù) 2.包含對外部作用域而非全局作用域的引用
下面通過一些實例來說明閉包函數(shù):
實例一:以下僅僅在函數(shù)內(nèi)部定義了一個函數(shù),但并非閉包函數(shù).
def outer():
def inner():
print("inner func excuted")
inner() # 調(diào)用執(zhí)行inner()函數(shù)
print("outer func excuted")
outer() # 調(diào)用執(zhí)行outer函數(shù)
####輸出結果為##########
inner func excuted
outer func excuted
實例二:以下在函數(shù)內(nèi)部定義了一個函數(shù),而且還引用了一個外部變量x,那么這個是閉包函數(shù)么?答案:不是
x = 1
def outer():
def inner():
print("x=%s" %x) # 引用了一個非inner函數(shù)內(nèi)部的變量
print("inner func excuted")
inner() # 執(zhí)行inner函數(shù)
print("outer func excuted")
outer()
#####輸出結果########
x=1
inner func excuted
outer func excuted
在回頭來看看對閉包函數(shù)的定義,是不是兩條都滿足?聰明的你,一定發(fā)現(xiàn)不滿足第二條.對,這里的變量x,是屬于全局變量,而非外部作用于域的變量。再來看看下面例子:
def outer():
x = 1
def inner():
print("x=%s" %x)
print("inner func excuted")
inner()
print("outer func excuted")
outer()
#####輸出結果#########
x=1
inner func excuted
outer func excuted
顯然,上面實例滿足閉包函數(shù)的條件。現(xiàn)在,你應該清楚,作為一個閉包函數(shù),必須得滿足上述的兩個條件,缺一不可。但是,一般情況下,我們都會給閉包函數(shù)返回一個值.這里先不說為什么.在接下來的內(nèi)容中,你會看到這個返回值的用途.
def outer():
x = 1
def inner():
print("x=%s" %x)
print("inner func excuted")
print("outer func excuted")
return inner # 返回內(nèi)部函數(shù)名
outer()
現(xiàn)在我們來抽象的定義一下閉包函數(shù)。它是函數(shù)和與其相關的引用環(huán)境組合而成的實體。在實現(xiàn)深約束時,需要創(chuàng)建一個能顯式表示引用環(huán)境的東西,并將它與相關的子程序捆綁在一起,這樣捆綁起成為閉包。在上面實例中,我們可以發(fā)現(xiàn),閉包函數(shù),它必須包含自己的函數(shù)以及一個外部變量才能真正稱得上是一個閉包函數(shù)。如果沒有一個外部變量與其綁定,那么這個函數(shù)不能算得上是閉包函數(shù)。
那么怎么知道一個閉包函數(shù)有多少個外部引用變量呢?看看下面代碼.
def outer():
x = 1
y = 2
def inner():
print("x= %s" %x)
print("y= %s" %y)
print(inner.__closure__)
return inner
outer()
######輸出結果#######
(<cell at 0x000000DF9EA965B8: int object at 0x000000006FC2B440>, <cell at 0x000000DF9EA965E8: int object at 0x000000006FC2B460>)
結果表明,在inner內(nèi)部,引用了兩個外部局部變量。如果引用的是非局部變量,那么這里輸出的為None.
閉包函數(shù)的特點:
1.自帶作用域 2.延遲計算
那么閉包函數(shù)有什么作用呢?我們清楚的知道,閉包函數(shù)在定義時,一定會綁定一個外部環(huán)境。這個整體才能算的上是一個閉包函數(shù),那么我們可以利用這個綁定特性,來完成某些特殊的功能。
實例三:根據(jù)傳入的URL,來下載頁面源碼
from urllib.request import urlopen
def index(url)
def get()
return urlopen(url).read()
return get
python = index("http://www.python.org") # 返回的是get函數(shù)的地址
print(python()) # 執(zhí)行get函數(shù)《并且將返回的結果打印出來
baidu = index("http://www.baidu.com")
print(baidu())
有人可以會說,這個不滿足閉包函數(shù)的條件啊!我沒有引用非全局的外部變量啊。其實并非如此,給,我們之前說過,只要在函數(shù)內(nèi)部的變量都屬于函數(shù)。那么我在index(url),這個url也屬于函數(shù)內(nèi)部,只不過我們省略一步而已,所以上面那個函數(shù)也是閉包函數(shù)。
4.裝飾器
有了以上基礎,對于裝飾器就好理解了.
裝飾器:外部函數(shù)傳入被裝飾函數(shù)名,內(nèi)部函數(shù)返回裝飾函數(shù)名。
特點:1.不修改被裝飾函數(shù)的調(diào)用方式 2.不修改被裝飾函數(shù)的源代碼
a.無參裝飾器
有如下實例,我們需要計算一下代碼執(zhí)行的時間。
import time, random
def index():
time.sleep(random.randrange(1, 5))
print("welcome to index page")
根據(jù)裝飾器的特點,我們不能對index()進行任何修改,而且調(diào)用方式也不能變。這時候,我們就可以使用裝飾器來完成如上功能.
import time, random
def outer(func): # 將index的地址傳遞給func
def inner():
start_time = time.time()
func() # fun = index 即func保存了外部index函數(shù)的地址
end_time = time.time()
print("運行時間為%s"%(end_time - start_time))
return inner # 返回inner的地址
def index():
time.sleep(random.randrange(1, 5))
print("welcome to index page")
index = outer(index) # 這里返回的是inner的地址,并重新賦值給index
index()
但是,有些情況,被裝飾的函數(shù)需要傳遞參數(shù)進去,有些函數(shù)又不需要參數(shù),那么如何來處理這種變參數(shù)函數(shù)呢?下面來看看有參數(shù)裝飾器的使用情況.
b.有參裝飾器
def outer(func): # 將index的地址傳遞給func
def inner(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs) # fun = index 即func保存了外部index函數(shù)的地址
end_time = time.time()
print("運行時間為%s"%(end_time - start_time))
return inner # 返回inner的地址
下面來說說一些其他情況的實例。
如果被裝飾的函數(shù)有返回值
def timmer(func):
def wrapper(*args,**kwargs):
start_time = time.time()
res=func(*args,**kwargs) #res來接收home函數(shù)的返回值
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
def home(name):
time.sleep(random.randrange(1,3))
print('welecome to %s HOME page' %name)
return 123123123123123123123123123123123123123123
這里補充一點,加入我們要執(zhí)行被裝飾后的函數(shù),那么應該是如下調(diào)用方式:
home = timmer(home) # 等式右邊返回的是wrapper的內(nèi)存地址,再將其賦值給home,這里的home不在是原來的的那個函數(shù),而是被裝飾以后的函數(shù)了。像home = timmer(home)這樣的寫法,python給我們提供了一個便捷的方式------語法糖@.以后我們再要在被裝飾的函數(shù)之前寫上@timmer,它的效果就和home = timmer(home)是一樣的。
如果一個函數(shù)被多個裝飾器裝飾,那么執(zhí)行順序是怎樣的。
import time
import random
def timmer(func):
def wrapper():
start_time = time.time()
func()
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return wrapper
def auth(func):
def deco():
name=input('name: ')
password=input('password: ')
if name == 'egon' and password == '123':
print('login successful')
func() #wrapper()
else:
print('login err')
return deco
@auth # index = auth(timmer(index))
@timmer # index = timmer(index)
def index():
time.sleep(3)
print('welecome to index page')
index()
實驗結果表明,多個裝飾器裝飾一個函數(shù),其執(zhí)行順序是從下往上。
關于裝飾器,還有一些高級用法,有興趣的可以自己研究研究。
相關文章
使用Python實現(xiàn)插入100萬條數(shù)據(jù)到MySQL數(shù)據(jù)庫
這篇文章主要為大家詳細介紹了如何使用Python實現(xiàn)插入100萬條數(shù)據(jù)到MySQL數(shù)據(jù)庫,文中的示例代碼講解詳細,有需要的小伙伴可以參考一下2024-04-04
python celery beat實現(xiàn)定時任務的示例代碼
在日常工作中,我們常常會用到需要周期性執(zhí)行的任務,本文主要介紹了python celery beat實現(xiàn)定時任務的示例代碼,具有一定的參考價值,感興趣的可以了解一下2024-03-03
Python提取特定時間段內(nèi)數(shù)據(jù)的方法實例
今天小編就為大家分享一篇關于Python提取特定時間段內(nèi)數(shù)據(jù)的方法實例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-04-04
詳解Python中映射類型的內(nèi)建函數(shù)和工廠函數(shù)
這篇文章主要介紹了詳解Python中映射類型的內(nèi)建函數(shù)和工廠函數(shù),目前Python的內(nèi)建映射類型只有字典一種,需要的朋友可以參考下2015-08-08
教你如何用一行Python代碼實現(xiàn)GUI圖形界面
GUI(圖形用戶界面),顧名思義就是用圖形的方式,來顯示計算機操作的界面,更加方便且直觀。本文將用一行代碼實現(xiàn)GUI界面的制作,需要的可以參考一下2022-05-05
詳解?PyTorch?Lightning模型部署到生產(chǎn)服務中
這篇文章主要為大家介紹了如何將PyTorch?Lightning模型部署到生產(chǎn)服務中的詳細教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09

