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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python函数装饰器一篇入魂

發布時間:2024/1/8 python 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python函数装饰器一篇入魂 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、什么是裝飾器

裝飾器顧名思義就是裝飾的工具,指的是在不改變被裝飾對象的源代碼以及調用方式的前提下,為被裝飾對象增加額外的功能,裝飾器經常使用在以下幾個場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗、測試等應用場景

二、為何要有裝飾器

軟件的設計應該遵循開放封閉原則,即對擴展是開放的,而對修改是封閉的。我們對現有的代碼需要擴展功能時不能修改源代碼以及調用方式,否則稍有不慎,就會導致整個程序報錯,因為我們要修改的這段程序可能在很多地方調用,所以修改源代碼和調用方式是很不明智的。而裝飾器則可以在不修改源代碼和調用方式的情況為原程序拓展新的功能。

三、裝飾器的實現思路

現在有一段程序,用來計算0~n的和

def add(n):sum = 0for i in range(n):sum += ireturn sumres = add(100000) print(res)輸出: >> 4999950000

如果想計算一下這個函數的運行時間,在修改源代碼的情況下我們是這樣做的

import timedef add(n):start = time.time()sum = 0for i in range(n):sum += istop = time.time()print('function run time: %s'%(stop - start))return sumres = add(100000) print(res)輸出: >> function run time: 0.014251947402954102 >> 4999950000

上面的代碼雖然實現了為函數體擴展計算函數運行時間的功能,但是違背了我們開頭所說的開放封閉原則,為函數增加新功能是不能修改源代碼的!那么在不修改源代碼的情況下,如何為其加新功能呢?我們可以在調用的時候,計算函數體運行的時間

import timedef add(n):sum = 0for i in range(n):sum += ireturn sumstart = time.time() res = add(100000) print(res) stop = time.time() print('function run time: %s'%(stop - start))輸出: >> 4999950000 >> function run time: 0.014489889144897461

現在實現了在不修改源代碼以及調用方式的情況下,為原函數增加了計算運行時間的功能,既沒有違背開放封閉原則,也擴展了新功能,看似是完美,但是有沒有想過,我們可能在很多地方都調用了add函數,那我們要在每次調用的時候都寫這么一堆代碼?這完全是重復且冗余的代碼,說到重復冗余的代碼,有些同學就想到了,我們可以封裝成一個函數啊!嘖嘖~

import timedef add(n):sum = 0for i in range(n):sum += ireturn sumdef wrapper():start = time.time()res = add(100000)print(res)stop = time.time()print('function run time: %s'%(stop - start))res = wrapper() print(res)輸出: >> 4999950000 >> function run time: 0.015117883682250977 >> None

好了,現在我們將拓展的功能封裝成一個函數了,這樣我們就省去了一大堆的重復冗余代碼,但是大家有沒有察覺到,上面的代碼是有問題的

第一個問題: add 函數是有返回值的,但是我們的新函數wrapper(這里可以稱之為裝飾器了)返回值是None。

第二個問題 :調用 add 函數是需要傳一個參數進去的,而裝飾器函數卻沒有傳參,這不就違背了文章開頭提到的開放封閉原則了么

是的 所以我們還可以將函數優化一下
解決第一個問題:在裝飾器函數里面要把調用add函數返回的值給return出來
解決第二個問題:在裝飾器函數里面,把調用add函數時傳的值參數化

import timedef add(n):sum = 0for i in range(n):sum += ireturn sumdef wrapper(n):start = time.time()res = add(n)stop = time.time()print('function run time: %s'%(stop - start))return resres = wrapper(10000) print(res)輸出: >> function run time: 0.0009837150573730469 >> 49995000

至此,我們已經在不違背開放封閉原則的前提下為 add 函數拓展了計算運行時間的功能,這樣就大功告成了么? 不不不,我們現在只是實現了為 add 函數擴展了計算運行時間的功能而已,如果我們還需要為其他函數增加計算運行時間的功能怎么辦,難道要再寫一個嗎?裝飾器是通用的,所以我們要考慮一下,函數名能不能參數化?(函數對象的概念), 我們先嘗試一下把函數名參數化,看看會有什么問題~

import timedef add(n):sum = 0for i in range(n):sum += ireturn sumdef index(x,y):print('from index %s:%s'%(x,y))def wrapper(func,n):start = time.time()res = func(n)stop = time.time()print('function run time: %s'%(stop - start))return resres_add = wrapper(add,10000) print(res_add)wrapper(index,1,2)輸出: >> function run time: 0.0007648468017578125 >> 49995000File "/Users/qts/PycharmProjects/QTS/test/ll.py", line 45, in <module>res_index = wrapper(index,1,2) TypeError: wrapper() takes 2 positional arguments but 3 were given

將函數名參數化以后,新增index函數進行測試一下,調用wrapper為index函數增加擴展功能,出現了問題,什么問題呢?我們來看一下

第一個問題:index 函數是需要傳兩個參數的,但是裝飾器函數wrapper 只有一個位置形參可以傳值,另一個參數怎么傳?

首先解決第一個問題,被裝飾的對象有幾個參數我們不知道,那么我們可不可以用不定長參數來接收呢,這里就用到args,**kwags來接收傳進來的參數打包成一個元組或者字典,然后在函數體內使用args,**kwargs 把元組或者字典重新打散傳值給func,這樣就實現了我們給wrapper傳什么參數,func接收的就是什么參數

import timedef add(n):sum = 0for i in range(n):sum += ireturn sumdef index(x,y):print('from index %s:%s'%(x,y))def wrapper(func,*args,**kwargs):start = time.time()res = func(*args,**kwargs)stop = time.time()print('function run time: %s'%(stop - start))return resres_add = wrapper(add,10000) print(res_add)wrapper(index,1,2)輸出: >> function run time: 0.0012209415435791016 >> 49995000 >> from index 1:2 >> function run time: 1.0013580322265625e-05

第二個問題:我們在調用的時候多傳了一個形參func,因為我們需要通過func 傳入被裝飾的函數對象,裝飾器函數改動了形參,改變了原函數的調用方式,違背了開頭說的開放封閉原則

解決第二個問題就使用到了閉包函數的概念,不了解的小伙伴可以去百度一下,閉包函數其實就是第二種為函數體傳參的方式,通過閉包函數的方式我們解決了第二個問題,現在我們就已經實現了完整的無參裝飾器了。

import timedef add(n):sum = 0for i in range(n):sum += ireturn sumdef index(x,y):print('from index %s:%s'%(x,y))def outter(func):def wrapper(*args,**kwargs):start = time.time()res = func(*args,**kwargs)stop = time.time()print('function run time: %s'%(stop - start))return resreturn wrapperadd = outter(add) res = add(10000) print(add)index = outter(index) index(2,1)輸出: >> function run time: 0.0017659664154052734 >> 49995000 >> from index 2:1 >> function run time: 1.621246337890625e-05

另外提一點命名空間的概念,調用outter函數返回的是wrapper的函數對象,我們在全局作用域拿到后可以賦值給x,可以賦值給y,也可以賦值給原函數名,把原函數名指向函數對象給覆蓋,我們就像是在調用原函數名一樣調用裝飾器了,是不是很完美~,其實python就已經幫我們實現這件事了,這里要介紹一個令人快樂的東西叫語法糖

語法糖
在被裝飾對象上面加上@裝飾器,就相當于 add = outter(add) 然后我們調用add時,其實就是調用的outter函數返回的wrapper

import timedef outter(func):def wrapper(*args,**kwargs):start = time.time()res = func(*args,**kwargs)stop = time.time()print('function run time: %s'%(stop - start))return resreturn wrapper@outter def add(n):sum = 0for i in range(n):sum += ireturn sum@outter def index(x,y):print('from index %s:%s'%(x,y))res = add(10000) print(res)index(1,2)輸出: >> function run time: 0.001477956771850586 >> 49995000 >> from index 1:2 >> function run time: 3.314018249511719e-05

再多說一句哈,現在我們實現的裝飾器是不是天衣無縫了?我們在調用的時候完全不用關心原函數擴展了啥功能,而且也不用改變原來的調用方式,但是還是有一點區別的,比如函數自帶的一些方法 namedoc 還是有區別的,我們既然要偽裝了,就一定要力爭完美,這里可以使用 functools 的 wraps 方法

import time from functools import wrapsdef outter(func):@wraps(func)def wrapper(*args,**kwargs):start = time.time()res = func(*args,**kwargs)stop = time.time()print('function run time: %s'%(stop - start))return resreturn wrapper@outter def add(n):sum = 0for i in range(n):sum += ireturn sum@outter def index(x,y):print('from index %s:%s'%(x,y))

無參裝飾器講完了,后續有機會講一下有參裝飾器以及疊加裝飾器的用法~

總結

以上是生活随笔為你收集整理的python函数装饰器一篇入魂的全部內容,希望文章能夠幫你解決所遇到的問題。

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