描述符的瞭解:python
描述符協議:閉包
python描述符是一個「綁定行爲」的對象屬性,在描述符協議中,它能夠經過方法重寫屬性的訪問。這些方法有:函數
__get__, __set__, 和__delete__測試
若是這些方法中的任何一個被定義在一個對對象
象中,這個對象就是一個描述符總結下說人話:就是當一個類中實例化另外一個類的時候,經過本類來調用另外一個類的功能,控制實例對象 訪問 這個屬性作一些額外的操做遞歸
class Fee: #先定義一個Fee類
def __get__(self, instance, owner):
print('get:疏樓龍宿人間至帥')
def __set__(self, instance, value):
print('set:疏樓龍宿%s'%value)
def __delete__(self, instance):
print('delete:疏樓龍宿人間無敵')get
class Fdd: #在定義一個類Fdd
fee = Fee() #咱們在Fdd中實例化Feeclass
咱們能夠經過Fdd實例化來調用Fee中功能,如何實現呢?test
a = Fdd() #先實例化,接下來咱們須要瞭解功能的對應調用狀況import
a.fee # 即調用fee屬性,fee屬性是什麼,就是Fee實例化,這時候會觸發__get__功能
a.fee = '人間至帥' #即對fee屬性進行修改,會觸發類Fee中的__set__功能,將修改內容傳入參數value
del a.fee # 刪除a實例化中fee屬性,就是要刪掉Fee(),會觸發Fee類中__delete__屬性
裝飾器:重點啊重點啊重點啊!
什麼是裝飾器?
裝飾器本質上也是函數,他是用來修飾其餘函數,給其餘函數添加額外功能的
裝飾器原則:
1--不改變被修飾函數的源代碼
2--不改變被修飾函數的調用方式
裝飾器的本質是什麼?
高階函數+函數嵌套+閉包
咱們回顧下,什麼是高階函數?
--函數接受的參數是另外一個函數的函數名
--函數的返回值另外一個函數的函數名
知足上述兩個條件中的一個,能夠稱爲高階函數
【注意:若是返回值是這個函數自己的調用方式,那麼這是遞歸!】
咱們再回顧下,什麼閉包?
就是函數的返回值是其內部嵌套函數的函數名
按照上述的規則,咱們來推導下裝飾器應該是什麼樣子
好比,咱們須要測試一下isinstance()這個函數的運行時間
首先,根據原則要求,咱們不能改變被修飾函數的源代碼,也就是說,isinstance()函數寫完後要放在一邊,咱們先寫一個運行代碼:
def test_isinstance():
for i in range(0,100000):
if isinstance(i,int) is True:
pass
pass
因爲一次運行的時間過短,咱們運行10W次,
按照原則1,這個時候,這個代碼就不能動了,咱們應該如何測試他的時間呢?
導入時間模塊包,寫一段高階函數,將上述函數傳入其中做爲參數,在運行前,記錄下時間,而後運行該函數,運行完,再記錄下時間,作個差運算,得出時間差,即運行時間
import time
def test_time(fun):
star_time = time.time()
fun()
stop_time = time.time()
run_time = stop_time - star_time
print('運行時間是%s' %run_time)
OK,咱們已經完美的得出了運行時間,並且源代碼也沒動
那麼問題來,函數調用方式,按照test_isinstance()的方式調用,咱們沒有獲得運行時間。。。。。若是要同時獲得結果,咱們就要使用test_time(test_isinstance)這個方法,和原則2衝突
到目前爲止,咱們已經解決了需求問題,即測試代碼運行時間,那麼若是進一步使調用方式不變呢?咱們想到了高階函數中的返回函數名,若是我返回值是須要測試的函數的函數名呢?
代碼:
def test_isinstance():
for i in range(0,100000):
if isinstance(i,int) is True:
pass
pass
import time
def test_time(fun):
star_time = time.time()
fun()
stop_time = time.time()
run_time = stop_time - star_time
print('運行時間是%s' %run_time)
return test_isinstance
這個時候,咱們再作以下操做:
test_isinstance = test_time(test_isinstance)
問題彷佛解決了,我直接使用就能夠獲得結果
至此,咱們已經獲得了 裝飾器的雛形,至於最後一部複製問題,裝飾器的格式,能夠解決,又稱爲 語法糖
@test_time
def test_isinstance():
.......
.......
最開的時候@test_time的功能效果便是:
test_isinstance = test_time(test_isinstance)
仔細檢查後,發現咱們彷佛運行了兩次test_isinstance函數?
test_time中運行了一次,接受返回函數名後咱們加上括號,又運行了一次,問題來了,這下怎麼辦?
遇到問題,解決問題,多運行了一次,咱們看看能不能少運行一次
常規手段沒法達成目的,咱們回憶下裝飾器的的本質是什麼
高階函數(已有)+函數嵌套(無)+閉包(無)
咱們還有其餘手段能夠用,好比函數嵌套,和閉包
咱們要實現的終極目的是test_isinstance()的同時會讓上述的兩個函數都運行一次,且只運行一次,並且,要得到原函數結果,上述test_time函數已經能夠得到時間,同時喊運行了一次test_isinstance函數,若是咱們截取下他內部test_isinstance的運行結果,做爲本函數的返回值呢?
import time
def test_time(fun):
star_time = time.time()
res = fun()
stop_time = time.time()
run_time = stop_time - star_time
print('運行時間是%s' %run_time)
return res
咱們能夠發現,運行這個函數,咱們能夠獲得和運行test_isinstance函數同樣的結果,由於返回值就是他的運行結果
咱們彷佛離真相愈來愈近了
如同上面說的同樣,咱們須要test_isinstance()的時候,讓函數運行一次,而不是兩次,函數運行是加括號直接運行,少運行一次,咱們就要少一個括號,即test_isintance的時候不運行,test_isintance上面咱們等於了 test_time(test_istntance),也便是,這個時候,我不運行函數,等加上括號的時候,在運行函數,兼顧上述咱們測試出來所須要的全部功能【增長附加功能,獲取本函數原返回值】
通過思考和測試,使用閉包功能
import time
def test_time(fun):
def test():
star_time = time.time()
res = fun()
stop_time = time.time()
run_time = stop_time - star_time
print('運行時間是%s' %run_time)
return res
return test
解釋代碼:
在測試函數上掛上@test_time,即:test_isintance = test_time(test_isintance),這個時候,同等於test_isintance=test【由於裝飾器函數test_time的返回值是test,test爲內嵌函數】;當test_isintance加上括號()運行的時候,等於運行了test()而後會進行下面一系列操做,同時,會返回原函數的返回值。
至此,裝飾器函數出爐了。
增長問題:
若是我函數自己就有參數呢。。。。。。。
好比:test_isintance(1,2,3,4,5)
這個等於什麼?不就等於 test(1,2,3,4,5) 麼!
也就是說我內嵌函數中,有可能會有參數!代碼,須要普適性,這種狀況也須要考慮進去!參數形式能肯定麼?彷佛不能,一樣,咱們也不該該根據測試函數的參數特色再去修改函數。
咱們須要把參數傳給,那麼函數參數形參和實參的賦值關係主要分爲哪幾類?
位置參數、關鍵字參數,即*args **kwargs,咱們在test_isintance的括號內加上參數,也就是在test的括號內加上參數,多是0個,多是若干個,*args和**kwargs完美接收了上述參數,在本來的函數test_isintance()的參數傳給test()後,再由test在代碼塊內部傳給test_isintance()【即代碼中的fun()】
最後咱們的代碼應該以下:
import time
def test_time(fun):
def test(*args,**kwargs):
star_time = time.time()
res = fun(*args,**kwargs)
stop_time = time.time()
run_time = stop_time - star_time
print('運行時間是%s' %run_time)
return res
return test
至此,裝飾器徹底體
使用方式:在上述代碼已經提早寫好的狀況下,使用語法糖便可
@test_time
def test_isinstance(): for i in range(0,100000): if isinstance(i,int) is True: pass pass