python装饰器带参数函数二阶导数公式_一文搞定Python装饰器,看完面试不再慌
本文始發(fā)于個人公眾號:TechFlow,原創(chuàng)不易,求個關(guān)注
今天是Python專題的第12篇文章,我們來看看Python裝飾器。
一段囧事
差不多五年前面試的時候,我就領(lǐng)教過它的重要性。那時候我Python剛剛初學(xué)乍練,看完了廖雪峰大神的博客,就去面試了。我應(yīng)聘的并不是一個Python的開發(fā)崗位,但是JD當(dāng)中寫到了需要熟悉Python。我看網(wǎng)上的面經(jīng)說到Python經(jīng)常會問裝飾器,我當(dāng)時想的是裝飾器我已經(jīng)看過了,應(yīng)該問題不大……
沒想到面試的時候還真的問到了,面試官問我Python當(dāng)中的裝飾器是什么。由于緊張和遺忘,我支支吾吾了半天也沒答上來。我隱約聽到了電話那頭的一聲嘆息……
時隔多年,我已經(jīng)不記得那是一家什么公司了(估計規(guī)模也不大),但裝飾器很重要這個事情給我深深打下了烙印。
裝飾器本質(zhì)
如今如果再有面試官問我Python中的裝飾器是什么,我一句話就能給回答了,倒不是我裝逼,實際上也的確只需要一句話。Python中的裝飾器,本質(zhì)上就是一個高階函數(shù)。
你可能不太清楚高階函數(shù)的定義,沒關(guān)系,我們可以類比一下。在數(shù)學(xué)當(dāng)中高階導(dǎo)數(shù),比如二次導(dǎo)數(shù),表示導(dǎo)數(shù)的導(dǎo)數(shù)。那么這里高階函數(shù)自然就是函數(shù)的函數(shù),結(jié)合我們之前介紹過的函數(shù)式編程,也就是說是一個返回值是函數(shù)的函數(shù)。但是這個定義是充分不必要的,也就是說裝飾器是高階函數(shù),但是高階函數(shù)并不都是裝飾器。裝飾器是高階函數(shù)一種特殊的用法。
任意參數(shù)
在介紹裝飾器的具體使用之前,我們先來了解和熟悉一下Python當(dāng)中的任意參數(shù)。
Python當(dāng)中支持任意參數(shù),它寫成*args, **kw。表示的含義是接受任何形式的參數(shù)。
舉個例子,比如我們定義一個函數(shù):
def exp(a, b, c='3', d='f'):
print(a, b, c, d)
我們可以這樣調(diào)用:
args = [1, 3]
dt = {'c': 4, 'd': 5}
exp(*args, **dt)
最后輸出的結(jié)果是1, 3, 4, 5。也就是說我們用一個list和dict可以表示任何參數(shù)。因為Python當(dāng)中規(guī)定必選參數(shù)一定寫在可選參數(shù)的前面,而必選參數(shù)是可以不用加上名稱標(biāo)識的,也就是可以不用寫a=1,直接傳入1即可。那么這些沒有名稱標(biāo)識的必選參數(shù)就可以用一個list來表示,而可選參數(shù)是必須要加上名稱標(biāo)識的,這些參數(shù)可以用dict來表示,這兩者相加可以表示任何形式的參數(shù)。
注意我們傳入list和dict的時候前面加上了*和**,它表示將list和dict當(dāng)中的所有值展開。如果不加的話,list和dict會被當(dāng)成是整體傳入。
所以如果一個函數(shù)寫成這樣,它表示可以接受任何形式的參數(shù)。
def exp(*args, **kw):
pass
定義裝飾器
明白了任意參數(shù)的寫法之后,裝飾器就不難了。
既然我們可以用*args, **kw接受任何參數(shù)。并且Python當(dāng)中支持一個函數(shù)作為參數(shù)傳入另外一個函數(shù),如果我們把函數(shù)和這個函數(shù)的所有參數(shù)全部傳入另外一個函數(shù),那么不就可以實現(xiàn)代理了嗎?
還是剛才的例子,我們額外增加一個函數(shù):
def exp(a, b, c='3', d='f'):
print(a, b, c, d)
def agent(func, *args, **kwargs):
func(*args, **kwargs)
args = [1]
dt = {'b': 1, 'c': 4, 'd': 5}
agent(exp, *args, **dt)
裝飾器的本質(zhì)其實就是這樣一個agent函數(shù),但是如果使用的時候需要手動傳入會非常麻煩,使用起來不太方便。所以Python當(dāng)中提供了特定的庫,我們可以讓裝飾器以注解的方式使用,大大簡化操作:
from functools import wraps
def wrapexp(func):
def wrapper(*args, **kwargs):
print('this is a wrapper')
func(*args, **kwargs)
return wrapper
@wrapexp
def exp(a, b, c='3', d='f'):
print(a, b, c, d)
args = [1, 3]
dt = {'c': 4, 'd': 5}
exp(*args, **dt)
在這個例子當(dāng)中,我們定義了一個wrapexp的裝飾器。我們在其中的wrapper方法當(dāng)中實現(xiàn)了裝飾器的邏輯,wrapexp當(dāng)中傳入的參數(shù)func是一個函數(shù),wrapper當(dāng)中的參數(shù)則是func的參數(shù)。所以我們在wrapper當(dāng)中調(diào)用func(*args, **kw),就是調(diào)用打上了這個注解的函數(shù)本身。比如在這個例子當(dāng)中,我們沒有做任何事情,只是在原樣調(diào)用之前多輸出了一行’this is a wrapper',表示我們的裝飾器調(diào)用成功了。
裝飾器用途
我們理解了裝飾器的基本使用方法之后,自然而然地會問一個天然的問題,學(xué)會了它究竟有什么用呢?
如果你從上面的例子當(dāng)中沒有領(lǐng)會到裝飾器的強大,不如讓我用一個例子再來暗示一下。比如說你是一個程序員,辛辛苦苦做出了一個功能,寫了好幾千行代碼,上百個函數(shù),終于通過了審核上線了。這個時候,你的產(chǎn)品經(jīng)理找到了你說,經(jīng)過分析我們發(fā)現(xiàn)上線的功能運行速度不達(dá)標(biāo),經(jīng)常有請求超時,你能不能計算一下每個函數(shù)運行的耗時,方便我們找到需要優(yōu)化的地方?
這是一個非常合理的請求,但想想看你寫了上百個函數(shù),如果每一個函數(shù)都要手動添加時間計算,這要寫多少代碼?萬一哪個函數(shù)不小心改錯了,你又得一一檢查,并且如果要求嚴(yán)格的話你還得為每一個函數(shù)專門寫一個單元測試……
我想,正常的程序員應(yīng)該都會抗拒這個需求。
但是有了裝飾器就很簡單了,我們可以實現(xiàn)一個計算函數(shù)耗時的裝飾器,然后我們只需要給每一個函數(shù)加上注解就好了。
import time
from functools import wraps
def timethis(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end-start)
return result
return wrapper
這也是裝飾器最大的用途,可以在不修改函數(shù)內(nèi)部代碼的前提下,為它包裝一些額外的功能。
元信息
我們之前說過裝飾器的本質(zhì)是高階函數(shù),所以我們也可以和高階函數(shù)一樣來調(diào)用裝飾器,比如下面這樣:
def exp(a, b, c='3', d='f'):
print(a, b, c, d)
args = [1, 3]
dt = {'c': 4, 'd': 5}
f = wrapexp(exp)
f(*args, **dt)
這樣的方式得到的結(jié)果和使用注解是一樣的,也就是說我們加上注解的本質(zhì)其實就是調(diào)用裝飾器返回一個新的函數(shù)。
既然和高階函數(shù)是一樣的,那么就帶來了一個問題,我們使用的其實已經(jīng)不再是原函數(shù)了,而是一個由裝飾器返回的新函數(shù),雖然這個函數(shù)的功能和原函數(shù)一樣,但是一些基礎(chǔ)的信息其實已經(jīng)丟失了。
比如我們可以打印出函數(shù)的name來做個實驗:
正常的函數(shù)調(diào)用__name__返回的都是函數(shù)的名稱,但是當(dāng)我們加上了裝飾器的注解之后,就會發(fā)生變化,同樣,我們輸出加上了裝飾器注解之后的結(jié)果:
我們會發(fā)現(xiàn)輸出的結(jié)果變成了wrapper,這是因為我們實現(xiàn)的裝飾器內(nèi)部的函數(shù)叫做wrapper。不僅僅是__name__,函數(shù)內(nèi)部還有很多其他的基本信息,比如記錄函數(shù)內(nèi)描述的__doc__,__annotations__等等,這些基本信息被稱為是元信息,這些元信息由于我們使用注解發(fā)生了丟失。
有沒有什么辦法可以保留這些函數(shù)的元信息呢?
其實很簡單,Python當(dāng)中為我們提供了一個專門的裝飾器用來保留函數(shù)的元信息,我們只需要在實現(xiàn)裝飾器的wrapper函數(shù)當(dāng)中加上一個注解wraps即可。
def wrapexp(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('this is a wrapper')
func(*args, **kwargs)
return wrapper
加上了這個注解之后,我們再來檢查函數(shù)的元信息,會發(fā)現(xiàn)它和我們預(yù)期一致了。
總結(jié)
了解了Python中的裝飾器之后,再來看之前我們用過的@property, @staticmethod等注解,想必都能明白,它們背后的實現(xiàn)其實也是裝飾器。靈活使用裝飾器可以大大簡化我們的代碼,讓我們的代碼更加規(guī)范簡潔,還能靈活地實現(xiàn)一些特殊的功能。
裝飾器的用法很多,今天介紹的只是其中最基本的,在后續(xù)的文章當(dāng)中,還會繼續(xù)和大家分享它更多其他的用法。在文章開始的時候我也說了,裝飾器是Python進(jìn)階必學(xué)的技能之一。想要熟練掌握這門語言,靈活運用,看懂大佬的源碼,裝飾器是必須會的東西。
希望大家都能有所收獲,原創(chuàng)不易,厚顏求個贊和關(guān)注~
總結(jié)
以上是生活随笔為你收集整理的python装饰器带参数函数二阶导数公式_一文搞定Python装饰器,看完面试不再慌的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 买卖股票的最佳时机 需要用户自己判断
- 下一篇: 江西财经大学计算机排名2019,2019