目錄
- 一、裝飾器使用場景
- 二、為什么需要裝飾器
- 1、先來看一個簡單例子:
- 2、增加需求
- 3、又有需求
- 三、基礎(chǔ)裝飾器入門
- 1、裝飾器語法糖
- 2、對帶參數(shù)的函數(shù)進行裝飾
- 3、函數(shù)參數(shù)數(shù)量不確定
- 4、裝飾器帶參數(shù)
- 5、functools.wraps
- 6、實現(xiàn)帶參數(shù)和不帶參數(shù)的裝飾器自適應(yīng)
- 三、類裝飾器
一、裝飾器使用場景
經(jīng)常用于有切面需求的場景,比如:插入日志、性能測試、事務(wù)處理、緩存、權(quán)限校驗等場景。裝飾器是解決這類問題的絕佳設(shè)計,有了裝飾器,我們就可以抽離出大量與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。
概括的講,裝飾器的作用就是為已經(jīng)存在的對象添加額外的功能。
二、為什么需要裝飾器
1、先來看一個簡單例子:
def foo():
print('i am foo')
2、增加需求
現(xiàn)在有一個新的需求,希望可以記錄下函數(shù)的執(zhí)行日志,于是在代碼中添加日志代碼:
def foo():
print('i am foo')
print("foo is running")
3、又有需求
假設(shè)現(xiàn)在有100個函數(shù)需要增加這個需求,并且后續(xù)可能還要對這一百個函數(shù)都增加執(zhí)行前打印日志的需求,怎么辦?還一個個改嗎?
當(dāng)然不了,這樣會造成大量雷同的代碼,為了減少重復(fù)寫代碼,我們可以這樣做,重新定義一個函數(shù):專門處理日志 ,日志處理完之后再執(zhí)行真正的業(yè)務(wù)代碼。
def use_logging(func):
print("%s is running" % func.__name__)
func()
def bar():
print('i am bar')
use_logging(bar)
運行結(jié)果:
#bar is running
#i am bar
函數(shù)use_logging就是裝飾器,它把執(zhí)行真正業(yè)務(wù)方法的func包裹在函數(shù)里面,看起來像bar被use_logging裝飾了。在這個例子中,函數(shù)進入和退出時 ,被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。
通過以上use_logging函數(shù)我們增加了日志功能,不管以后有多少函數(shù)需要增加日志或者修改日志的格式我們只需要修改use_logging函數(shù),并執(zhí)行use_logging(被裝飾的函數(shù))就達到了我們想要的效果。
def use_logging(func):
print("%s is running" % func.__name__)
return func
@use_logging
def bar():
print('i am bar')
bar()
三、基礎(chǔ)裝飾器入門
1、裝飾器語法糖
python提供了@符號作為裝飾器的語法糖,使我們更方便的應(yīng)用裝飾函數(shù);但使用語法糖要求裝飾函數(shù)必須return一個函數(shù)對象。因此我們將上面的func函數(shù)使用內(nèi)嵌函數(shù)包裹并return。
裝飾器相當(dāng)于執(zhí)行了裝飾函數(shù)use_loggin后又返回被裝飾函數(shù)bar,因此bar()被調(diào)用的時候相當(dāng)于執(zhí)行了兩個函數(shù)。等價于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、對帶參數(shù)的函數(shù)進行裝飾
現(xiàn)在我們的參數(shù)需要傳入兩個參數(shù)并計算值,因此我們需要對內(nèi)層函數(shù)進行改動傳入我們的兩個參數(shù)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)
我們裝飾的函數(shù)可能參數(shù)的個數(shù)和類型都不一樣,每一次我們都需要對裝飾器做修改嗎?這樣做當(dāng)然是不科學(xué)的,因此我們使用python的變長參數(shù)*args和**kwargs來解決我們的參數(shù)問題。
3、函數(shù)參數(shù)數(shù)量不確定
不帶參數(shù)裝飾器版本,這個格式適用于不帶參數(shù)的裝飾器。
經(jīng)過以下修改,我們已經(jīng)適應(yīng)了各種長度和類型的參數(shù)。這個版本的裝飾器可以裝飾任意類型的無參數(shù)函數(shù)。
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、裝飾器帶參數(shù)
帶參數(shù)的裝飾器,這個格式適用于帶參數(shù)的裝飾器。
某些情況我們需要讓裝飾器帶上參數(shù),那就需要編寫一個返回一個裝飾器的高階函數(shù),寫出來會更復(fù)雜。比如:
#! /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
使用裝飾器極大地復(fù)用了代碼,但是他有一個缺點就是原函數(shù)的元信息不見了,比如函數(shù)的docstring、__name__、參數(shù)列表,先看例子:
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
#函數(shù)名變?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 ,這個結(jié)果是我們想要的。OK啦!
6、實現(xiàn)帶參數(shù)和不帶參數(shù)的裝飾器自適應(yīng)
import functools
def use_logging(arg):
if callable(arg):#判斷參入的參數(shù)是否是函數(shù),不帶參數(shù)的裝飾器調(diào)用這個分支
@functools.wraps(arg)
def _deco(*args,**kwargs):
print("%s is running" % arg.__name__)
arg(*args,**kwargs)
return _deco
else:#帶參數(shù)的裝飾器調(diào)用這個分支
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()
三、類裝飾器
使用類裝飾器可以實現(xiàn)帶參數(shù)裝飾器的效果,但實現(xiàn)的更加優(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的實現(xiàn)版本,可以在函數(shù)調(diào)用時發(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 裝飾器的使用與要點的詳細內(nèi)容,更多關(guān)于python 裝飾器的資料請關(guān)注腳本之家其它相關(guān)文章!
您可能感興趣的文章:- python中有函數(shù)重載嗎
- 在Python中實現(xiàn)函數(shù)重載的示例代碼
- python裝飾器原理源碼示例分析
- Python Pytest裝飾器@pytest.mark.parametrize詳解
- 理解python中裝飾器的作用
- 詳解Python裝飾器 給你的咖啡加點料
- 如何正確理解python裝飾器
- 如何利用飾器實現(xiàn) Python 函數(shù)重載