一、裝飾器是什么
python的裝飾器本質上是一個Python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值也是一個函數對象。簡單的說裝飾器就是一個用來返回函數的函數。
它經常用於有切面需求的場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼並繼續重用。
概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。
二、為什么需要裝飾器
1、先來看一個簡單例子:
1
2
|
def
foo():
print
(
'i am foo'
)
|
2、增加需求
現在有一個新的需求,希望可以記錄下函數的執行日志,於是在代碼中添加日志代碼:
1
2
3
|
def
foo():
print
(
'i am foo'
)
print
(
"foo is running"
)
|
3、又有需求
假設現在有100個函數需要增加這個需求,並且后續可能還要對這一百個函數都增加執行前打印日志的需求,怎么辦?還一個個改嗎?
當然不了,這樣會造成大量雷同的代碼,為了減少重復寫代碼,我們可以這樣做,重新定義一個函數:專門處理日志 ,日志處理完之后再執行真正的業務代碼。
1
2
3
4
5
6
7
8
9
|
def
use_logging(func):
print
(
"%s is running"
%
func.__name__)
func()
def
bar():
print
(
'i am bar'
)
use_logging(bar)
#result:
#bar is running
#i am bar
|
通過以上use_logging函數我們增加了日志功能,不管以后有多少函數需要增加日志或者修改日志的格式我們只需要修改use_logging函數,並執行use_logging(被裝飾的函數)就達到了我們想要的效果。
1
2
3
4
5
6
7
|
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。
裝飾器相當於執行了裝飾函數use_loggin后又返回被裝飾函數bar,因此bar()被調用的時候相當於執行了兩個函數。等價於use_logging(bar)()
1
2
3
4
5
6
7
8
9
|
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)
1
2
3
4
5
6
7
8
9
|
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、函數參數數量不確定
不帶參數裝飾器版本,這個格式適用於不帶參數的裝飾器。
經過以下修改我們已經適應了各種長度和類型的參數。這個版本的裝飾器已經可以任意類型的無參數函數。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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、裝飾器帶參數
帶參數的裝飾器,這個格式適用於帶參數的裝飾器。
某些情況我們需要讓裝飾器帶上參數,那就需要編寫一個返回一個裝飾器的高階函數,寫出來會更復雜。比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#! /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__、參數列表,先看例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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
#函數名變為_deco而不是bar,這個情況在使用反射的特性的時候就會造成問題。因此引入了functools.wraps解決這個問題。
|
使用functools.wraps:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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、實現帶參數和不帶參數的裝飾器自適應
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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()
|
三、類裝飾器
使用類裝飾器可以實現帶參數裝飾器的效果,但實現的更加優雅簡潔,而且可以通過繼承來靈活的擴展.
1、類裝飾器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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"
)
#執行__call__方法
def
bar(a,b):
print
(
'i am bar:%s'
%
(a
+
b))
bar(
1
,
3
)
|
2、繼承擴展類裝飾器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class
email_loging(Loging):
'''
一個loging的實現版本,可以在函數調用時發送email給管理員
'''
def
__init__(
self
, email
=
'admin@myproject.com'
,
*
args,
*
*
kwargs):
self
.email
=
email
super
(email_loging,
self
).__init__(
*
args,
*
*
kwargs)
def
notify(
self
,func):
# 發送一封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
)
|