日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

python重难点之装饰器详解

發(fā)布時間:2023/11/28 生活经验 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python重难点之装饰器详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

背景

雖然之前看過裝飾器的相關內容,但是今天想起來,一直沒有好好總結一下,所以特地記錄下關于裝飾器的一系列用法。
要想理解裝飾器首先要明確頗python中的三個概念:
1.一切函數(shù)皆為對象
2.高階函數(shù)
3.嵌套函數(shù)
然后才能理解:
4.什么是裝飾器?
5.裝飾器如何實現(xiàn)?
6.裝飾器有什么用?

詳細解釋

一切函數(shù)皆為對象

準確來說在Python,一切皆為對象,此處說的點與函數(shù)相關所以將范圍縮小了一下。看一個最基本的例子,我們可以發(fā)現(xiàn)我們可以直接將test1像變量一樣賦值給a,然后a可以像函數(shù)一樣使用。

def test1():print('i am test1')# 可將test1想變量一樣賦值給a,然后a便可以像函數(shù)一樣使用
a = test1
a()
# output:
# i am test1

高階函數(shù)

函數(shù)參數(shù)可以接受變量,那么一個函數(shù)可以接受另一個函數(shù)作為參數(shù),或者返回值為函數(shù)(這個稍后再說),那這種函數(shù)稱之為高階函數(shù),如下面這段代碼:

def add (a, b, f):return f(a) + f(b)
res = add(3, -6, abs)
print(res)

在這段代碼代碼中,add()就是一個高階函數(shù),可以看見在add()中的參數(shù)中有將abs()這個取絕對值函數(shù)做為參數(shù)。

嵌套函數(shù)

在一個函數(shù)的函數(shù)體內用def去申明一個函數(shù),這樣的函數(shù)叫做嵌套函數(shù),如下面這段代碼:

def foo():print('in the foo')def bar():print('in the bar')bar()
foo()
# outputs:
# in the foo
# in the bar

通過調用foo(),將在foo()內部定義并且調用bar()。我們是稍微改動一下代碼:

def foo():print('in the foo')def bar():print('in the bar')return bar # 將bar作為foo()的返回值
a = foo()

上面這段代碼稍微改了一下foo的返回值,即將bar作為foo的返回值返回了,還記得上面所說的高階函數(shù)的定義么?這就是將函數(shù)返回的類型,然后我們將foo()賦值給變量a,這不就是我們呢所說的函數(shù)即變量的概念么?
最后得到輸出:

in the foo

如果我們在后面再加一句:

a()

將會輸出:

in the bar

這表明現(xiàn)在這個a已經是個函數(shù)類型了,他的功能就是foo()函數(shù)里面bar()的功能。

什么是裝飾器?

說了這么多鋪墊,那到底什么是裝飾器呢?
其實裝飾器的本質還是函數(shù),它是為了裝飾其他函數(shù)的,說白了就是為其他函數(shù)添加附加功能的
具體什么意思呢?比如我們我們之前寫了一個函數(shù),我們現(xiàn)在想在這個函數(shù)上添加一些功能,但是我們又不能改變在原來的函數(shù)基本上修改,而且還不能修改它的調用方式,因為它可能在很多地方已經被調用了,所以我們就必須要搞一個裝飾器,來給原來的函數(shù)裝飾一下,便于實現(xiàn)新的功能。
那裝飾器應該怎么弄?我用一個公式來概括一下:
高階函數(shù) + 嵌套函數(shù) ----> 裝飾器
即通過高階函數(shù)和嵌套函數(shù)我們就可以實現(xiàn)一個裝飾器

如何實現(xiàn)一個裝飾器

假設我們有一個函數(shù)func()如下,我現(xiàn)在想知道這個函數(shù)總共運行了多長時間,并且打印出來,而且我不能修改func()本身,并且不可以該變它的調用方式,那怎么辦?

import timedef func():time.sleep(2) #模擬一系列的操作print('i am func in 1')

我們經過思考,結合上文可以寫下如下的函數(shù):

def func_time(func):start_time = time.time()func()end_time = time.time()print('func time is ',end_time - start_time)func_time(func) 
# outputs:
# i am func in 1
# func time is  2.0014798641204834

我們可以發(fā)現(xiàn)func_time()是可以統(tǒng)計func()的運行時間的,但是我想要的結果是直接使用func()就可以出現(xiàn)這樣的效果,而且以后每次這么用,都會有這樣的效果啊。
于是我們想起一切函數(shù)皆為對象,以及嵌套函數(shù)、高階函數(shù)的用法,再改一改,得到如下的代碼:

def func_time(func):def wrapper():start_time = time.time()func()end_time = time.time()print('func time is ',end_time - start_time)return wrapperfunc = func_time(func)
func()

我們在func_time()中使用嵌套函數(shù)定義了一個wrapper()函數(shù),然后將剛才的操作都放在這個wrapper()中了,最后我們將wrapper作為func_time()的返回值返回了,在函數(shù)外面我們將func_time(func)又賦值給了func(),即將wrapper賦值給了func(),也就是說func()其實是實現(xiàn)的wrapper()的功能,而在wrapper中不但有func()的功能而且還有計算func()運行時間的功能。最后我們每次調用func()就會實現(xiàn)計時的功能了。
到這里其實我們已經手動的實現(xiàn)了python的裝飾器功能了,但是有沒有更簡單的方法呢?是有的,在python中提供了一個裝飾器語法,在上面的這個例子中,我們只需在func()函數(shù)前面加上一句@func_time。
@ 符號就是裝飾器的語法糖,它放在函數(shù)開始定義的地方,這樣就可以省略最后一步再次賦值的操作。什么是語法糖?就是計算機添加的某種語法,對語言的功能沒有影響,但方便程序員使用
然后我們直接就可以使用func()即可,完整的也就是:

@func_time
def func():time.sleep(2)print('i am func in 1')

也就是說@func_time其實就是等價于

func = func_time(func)

還有一點需要注意的是func_time()這個函數(shù)的定義一定要寫在func()定義前面,要不然使用@func_time,python在內存中是找不到func_time()的位置的。
完整的代碼是這樣的:

import timedef func_time(func):def wrapper():start_time = time.time()func()end_time = time.time()print('func time is ',end_time - start_time)return wrapper@func_time
def func():time.sleep(2)print('i am func in 1')func()

到此你就實現(xiàn)了一個基本的裝飾器,但是如果你還不滿足,請繼續(xù)向下看。

-------------------------------------華麗的分割線------------------------------------------

如何實現(xiàn)一個裝飾器(進階)

裝飾器還可以怎么用?首先第一個就是裝飾器可以累計使用
現(xiàn)在我有一個函數(shù):

def say():return "Hello"

我希望它可以根據不同的需要實現(xiàn)以下兩種輸出,不定時切換:

<b><i>Hello</i></b>
<i><b>Hello</b></i>

我們可以用裝飾器很輕易的實現(xiàn),我們先實現(xiàn)兩個裝飾器:

# 用來裝飾say()產生<b></b>
def makebold(fn):def wrapper():return "<b>" + fn() + "</b>"return wrapper# 用來裝飾say()產生<i></i>
def makeitalic(fn):def wrapper():return "<i>" + fn() + "</i>"return wrapper

然后我們來裝飾say():

@makebold
@makeitalic
def say():return "hello"print(say())

通過上面的裝飾我們可以輸出:

<b><i>hello</i></b>

如果我們將兩個裝飾器的位置改變一下即:

@makeitalic
@makebold
def say():return "hello"print(say())

我們就可以輸出:

<i><b>hello</b></i>

如何實現(xiàn)一個裝飾器(高階)

我們應該如何給一個被修飾的函數(shù)傳遞參數(shù)呢?
其實當你調用被裝飾器返回的函數(shù)時,實際你是在調用封裝函數(shù) ,向封裝函數(shù)傳遞參數(shù)可以讓封裝函數(shù)把參數(shù)傳遞給被裝飾的函數(shù)。
我們先來看看在最開始統(tǒng)計函數(shù)運行時間的小程序上做的一點改變后的樣子:

def count(func):def sleep_time(name): # 傳入參數(shù)start_time = time.time()func(name)stop_time = time.time()print(stop_time - start_time)return sleep_time@count
def sleep2(name):time.sleep(2)print(name, 'sleepping in 2') #打印參數(shù)sleep2('sty')
# outputs:
# sty sleepping in 2
# 2.002162218093872

我們可以看到’sty’已經作為參數(shù)傳進入了,但是如果我們有的時候我們對有的被裝飾函數(shù)我們不需要參數(shù),或者我們不確定有多少個參數(shù)該怎么辦?我們總不能再重復的寫裝飾器吧?這個時候我們就需要使用到*args,**kwargs了,在python中參數(shù)有四類:必選參數(shù)、默認參數(shù)、可變參數(shù)和關鍵字參數(shù),這4種參數(shù)都可以一起使用,或者只用其中某些,但是請注意,參數(shù)定義的順序必須是:必選參數(shù)、默認參數(shù)、可變參數(shù)和關鍵字參數(shù),關于具體的詳細介紹點這里。

*args:接受N個位置參數(shù),轉換為元組的形式
**kwargs:接受N個位置參數(shù),轉換為字典的形式

這樣我們就可以對我們的裝飾器進行一定的改進了,如下面的代碼:

import timedef count(func):def sleep_time(*args,**kwargs):start_time = time.time()func(*args,*kwargs)stop_time = time.time()print(stop_time - start_time)return sleep_time
@count
def sleep1():time.sleep(2)print('i am sleepping in 1')@count
def sleep2(name):time.sleep(2)print(name, 'sleepping in 2')sleep1()
sleep2('sty')
# outputs:
# i am sleepping in 1
# 2.0013535022735596
# sty sleepping in 2
# 2.0010647773742676

在這段代碼中我們利用裝飾器裝飾了兩個函數(shù)sleep()和sleep1(),sleep()沒有傳參數(shù),sleep1()傳了一個參數(shù),發(fā)現(xiàn)都可以完好運行。

一個問題需要引起你的注意:
在上面的代碼最后添加下面的兩行,我們打印下sleep1()和sleep2()的名字

print('sleep1 name is :', sleep1.__name__)
print('sleep2 name is :', sleep2.__name__)

得到的結果是:

sleep1 name is : sleep_time
sleep2 name is : sleep_time

雖然我們知道sleep1()和sleep2()就是執(zhí)行的sleep_time()的功能,但是我們還是不愿意看見它的真實名字改變。這并不是我們想要的!我們希望的結果輸出應該是:

sleep1 name is : sleep1
sleep2 name is : sleep2

這里的函數(shù)被sleep_time()替代了。它重寫了我們函數(shù)的名字和注釋文檔(docstring)。幸運的是Python提供給我們一個簡單的函數(shù)來解決這個問題,那就是functools.wraps。我們修改一下上例, 在封裝函數(shù)前加上@wraps(func),可以得到:

from functools import wraps
import timedef count(func):@wraps(func)    #在封裝函數(shù)前加上def sleep_time(*args,**kwargs):start_time = time.time()func(*args,*kwargs)stop_time = time.time()print(stop_time - start_time)return sleep_time
@count
def sleep1():time.sleep(2)print('i am sleepping in 1')@count
def sleep2(name):time.sleep(2)print(name, 'sleepping in 2')sleep1()
sleep2('sty')
print('sleep1 name is :', sleep1.__name__)
print('sleep2 name is :', sleep2.__name__)
#Outputs:
# i am sleepping in 1
# 2.0020651817321777
# sty sleepping in 2
# 2.002112627029419
# sleep1 name is : sleep1
# sleep2 name is : sleep2

裝飾器實戰(zhàn)之授權認證

需求:假如現(xiàn)在一個網站有三個頁面,index, home, bbs, 其中index頁面是你不需要登錄就可以查看的,而home,bbs是需要登錄才能查看的,并且告訴我登錄授權形式,有l(wèi)ocal和remote兩種可以選擇,我們應該怎么做?

解決方法:裝飾器高階用法,需要給裝飾器一開始就傳參數(shù),需要在裝飾器中再寫一個函數(shù)傳遞被裝飾函數(shù)。說的可能有點繞,具體查看下面的代碼:

from functools import wrapsuser, passwd = 'sty', '1234'  # 定義一個默認的用戶名和密碼
def auth(auth_type):print('now auth_type is:', auth_type)def outer_wrapper(func): # 又設了一層函數(shù),來傳遞被裝飾函數(shù)的@wraps(func)def wrapper(*args, **kwargs):user_name = input("UserName:").strip()pass_word = input("PassWord:").strip()if user == user_name and passwd == pass_word:print("\033[32;1mUser has passed authentication\033[0m")  #讓打印帶顏色顯示func(*args, **kwargs)else:print("\033[31;1mInvalid username or passward\033[0m")return wrapper  return outer_wrapperdef index():print("welcome to the index page")@auth(auth_type='local') #等價于home = auth(auth_type='local')(home)
def home():print("welcome to the home page")@auth(auth_type='remote') #等價于home = auth(auth_type='ldap')(home)
def bbs():print("welcome to the bbs page")index()
print('login in home, input name and password:')
home()
print('login in bbs: input name and password:')
bbs()

我們注意到:
@auth(auth_type=‘local’) 等價于home = auth(auth_type=‘local’)(home)
@auth(auth_type=‘remote’) 等價于home = auth(auth_type=‘ldap’)(home)
當這樣看的時候我們就不難理解它是如何實現(xiàn)的了
到此裝飾器的幾乎大部分功能就差不多了,其他的一些具體用法還需要你逐漸去探索

最后提一句

在學習裝飾器的時候,如果你還想從更加深入的去理解,那你就得需要知道’閉包’,關于什么是閉包?以及它和裝飾器的關系?有時間將在接下來的博客中提到。

參考文檔:

https://stackoverflow.com/questions/739654/how-to-make-a-chain-of-function-decorators
https://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python
https://segmentfault.com/a/1190000004461404
http://www.cnblogs.com/itech/archive/2011/12/31/2308640.html
http://www.cnblogs.com/vamei/archive/2012/12/15/2772451.html

轉載請注明出處:
CSDN:樓上小宇_home:http://blog.csdn.net/sty945
簡書:樓上小宇:http://www.jianshu.com/u/1621b29625df

總結

以上是生活随笔為你收集整理的python重难点之装饰器详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。