python 裝飾器的使用與要點
一、裝飾器使用場景
經常用于有切面需求的場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼并繼續(xù)重用。
概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。
二、為什么需要裝飾器
1、先來看一個簡單例子:
def foo(): print('i am foo')
2、增加需求
現在有一個新的需求,希望可以記錄下函數的執(zhí)行日志,于是在代碼中添加日志代碼:
def foo(): print('i am foo') print("foo is running")
3、又有需求
假設現在有100個函數需要增加這個需求,并且后續(xù)可能還要對這一百個函數都增加執(zhí)行前打印日志的需求,怎么辦?還一個個改嗎?
當然不了,這樣會造成大量雷同的代碼,為了減少重復寫代碼,我們可以這樣做,重新定義一個函數:專門處理日志 ,日志處理完之后再執(zhí)行真正的業(yè)務代碼。
def use_logging(func): print("%s is running" % func.__name__) func() def bar(): print('i am bar') use_logging(bar) 運行結果: #bar is running #i am bar
函數use_logging就是裝飾器,它把執(zhí)行真正業(yè)務方法的func包裹在函數里面,看起來像bar被use_logging裝飾了。在這個例子中,函數進入和退出時 ,被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。
通過以上use_logging函數我們增加了日志功能,不管以后有多少函數需要增加日志或者修改日志的格式我們只需要修改use_logging函數,并執(zhí)行use_logging(被裝飾的函數)就達到了我們想要的效果。
def use_logging(func): print("%s is running" % func.__name__) return func @use_logging def bar(): print('i am bar') bar()
三、基礎裝飾器入門
1、裝飾器語法糖
python提供了@符號作為裝飾器的語法糖,使我們更方便的應用裝飾函數;但使用語法糖要求裝飾函數必須return一個函數對象。因此我們將上面的func函數使用內嵌函數包裹并return。
裝飾器相當于執(zhí)行了裝飾函數use_loggin后又返回被裝飾函數bar,因此bar()被調用的時候相當于執(zhí)行了兩個函數。等價于use_logging(bar)()
def use_logging(func): def _deco(): print("%s is running" % func.__name__) func() return _deco @use_logging def bar(): print('i am bar') bar()
2、對帶參數的函數進行裝飾
現在我們的參數需要傳入兩個參數并計算值,因此我們需要對內層函數進行改動傳入我們的兩個參數a和b,等價于use_logging(bar)(1,2)
def use_logging(func): def _deco(a,b): print("%s is running" % func.__name__) func(a,b) return _deco @use_logging def bar(a,b): print('i am bar:%s'%(a+b)) bar(1,2)
我們裝飾的函數可能參數的個數和類型都不一樣,每一次我們都需要對裝飾器做修改嗎?這樣做當然是不科學的,因此我們使用python的變長參數*args和**kwargs來解決我們的參數問題。
3、函數參數數量不確定
不帶參數裝飾器版本,這個格式適用于不帶參數的裝飾器。
經過以下修改,我們已經適應了各種長度和類型的參數。這個版本的裝飾器可以裝飾任意類型的無參數函數。
def use_logging(func): def _deco(*args,**kwargs): print("%s is running" % func.__name__) func(*args,**kwargs) return _deco @use_logging def bar(a,b): print('i am bar:%s'%(a+b)) @use_logging def foo(a,b,c): print('i am bar:%s'%(a+b+c)) bar(1,2) foo(1,2,3)
4、裝飾器帶參數
帶參數的裝飾器,這個格式適用于帶參數的裝飾器。
某些情況我們需要讓裝飾器帶上參數,那就需要編寫一個返回一個裝飾器的高階函數,寫出來會更復雜。比如:
#! /usr/bin/env python # -*- coding:utf-8 -*- # __author__ = "TKQ" def use_logging(level): def _deco(func): def __deco(*args, **kwargs): if level == "warn": print "%s is running" % func.__name__ return func(*args, **kwargs) return __deco return _deco @use_logging(level="warn") def bar(a,b): print('i am bar:%s'%(a+b)) bar(1,3) # 等價于use_logging(level="warn")(bar)(1,3)
5、functools.wraps
使用裝飾器極大地復用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表,先看例子:
def use_logging(func): def _deco(*args,**kwargs): print("%s is running" % func.__name__) func(*args,**kwargs) return _deco @use_logging def bar(): print('i am bar') print(bar.__name__) bar() #bar is running #i am bar #_deco #函數名變?yōu)開deco而不是bar,這個情況在使用反射的特性的時候就會造成問題。因此引入了functools.wraps解決這個問題。
使用functools.wraps:
import functools def use_logging(func): @functools.wraps(func) def _deco(*args,**kwargs): print("%s is running" % func.__name__) func(*args,**kwargs) return _deco @use_logging def bar(): print('i am bar') print(bar.__name__) bar() #result: #bar is running #i am bar #bar ,這個結果是我們想要的。OK啦!
6、實現帶參數和不帶參數的裝飾器自適應
import functools def use_logging(arg): if callable(arg):#判斷參入的參數是否是函數,不帶參數的裝飾器調用這個分支 @functools.wraps(arg) def _deco(*args,**kwargs): print("%s is running" % arg.__name__) arg(*args,**kwargs) return _deco else:#帶參數的裝飾器調用這個分支 def _deco(func): @functools.wraps(func) def __deco(*args, **kwargs): if arg == "warn": print "warn%s is running" % func.__name__ return func(*args, **kwargs) return __deco return _deco @use_logging("warn") # @use_logging def bar(): print('i am bar') print(bar.__name__) bar()
三、類裝飾器
使用類裝飾器可以實現帶參數裝飾器的效果,但實現的更加優(yōu)雅簡潔,而且可以通過繼承來靈活的擴展.
1、類裝飾器
class loging(object): def __init__(self,level="warn"): self.level = level def __call__(self,func): @functools.wraps(func) def _deco(*args, **kwargs): if self.level == "warn": self.notify(func) return func(*args, **kwargs) return _deco def notify(self,func): # logit只打日志,不做別的 print "%s is running" % func.__name__ @loging(level="warn")#執(zhí)行__call__方法 def bar(a,b): print('i am bar:%s'%(a+b)) bar(1,3)
2、繼承擴展類裝飾器
class email_loging(Loging): ''' 一個loging的實現版本,可以在函數調用時發(fā)送email給管理員 ''' def __init__(self, email='admin@myproject.com', *args, **kwargs): self.email = email super(email_loging, self).__init__(*args, **kwargs) def notify(self,func): # 發(fā)送一封email到self.email print "%s is running" % func.__name__ print "sending email to %s" %self.email @email_loging(level="warn") def bar(a,b): print('i am bar:%s'%(a+b)) bar(1,3)
以上就是python 裝飾器的使用與要點的詳細內容,更多關于python 裝飾器的資料請關注本站其它相關文章!
版權聲明:本站文章來源標注為YINGSOO的內容版權均為本站所有,歡迎引用、轉載,請保持原文完整并注明來源及原文鏈接。禁止復制或仿造本網站,禁止在非www.sddonglingsh.com所屬的服務器上建立鏡像,否則將依法追究法律責任。本站部分內容來源于網友推薦、互聯網收集整理而來,僅供學習參考,不代表本站立場,如有內容涉嫌侵權,請聯系alex-e#qq.com處理。