Python Decorator

Python Decorator

Why Decorator

試想一個場景,如何在不變動下述程式片段的情況下,在進入Function時,印出Function名稱?

1
2
def foo():
print('Hello foo!')

Simple Decorator

利用Python的Functional programming特性,將要裝飾(Decorate)的Function作爲另一個Function參數,來達成上述目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def print_function_name(func):
def wrapper():
print("function name is %s " % func.__name__)
return func()
return wrapper
def foo():
print('Hello foo!')
foo = print_function_name(foo)
foo()
'''
function name is foo
Hello foo!
'''

print_function_name function就是一個簡單的裝飾器。其實就是一個接收一個Function形態的參數,而且返回的也是一個Function。
上述說明與程式變量的對應:

  • Function形態的參數: func
  • 返回的也是一個Function: wrapper

Syntax sugar @

利用Syntax sugar @ 可以達成相同效果且更爲簡潔的語法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def print_function_name(func):
def wrapper():
print("function name is %s " % func.__name__)
return func()
return wrapper
@print_function_name
def foo():
print('Hello foo!')
foo()
'''
function name is foo
Hello foo!
'''

Applications

透過上述可知裝飾器(Decorator)有下述的應用場景:

  • 在程式運行期間動態增加Function的功能,而且不能影響目標Function的場景
  • 要重覆使用某些程式片段的場景

而上述就是AOP(Aspect Oriented Program)的要解決的場景之一。常見的應用場景爲日誌插入、權限校驗等

Decorator Description

  • 本質上是Python的Function或Class
  • 是閉包(Closure)的一種
  • 接收一個Function形態的參數
  • 返回一個Function形態的物件,該Function會對接收的Function進行包裝或操作
    • 接收的Function可以是Generator,達成與Decorator交互執行目的

Decorator Execution Order

基於Syntax sugar的版本新增一些Log,觀察整個調用順序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def print_function_name(func):
print("---> print_function_name: %s" % func.__name__)
def wrapper():
print("function name is %s " % func.__name__)
return func()
print("<--- print_function_name")
return wrapper
@print_function_name
def foo():
print('Hello foo!')
print("=== call function ===")
foo()
print("=== call function done ===")
'''
---> print_function_name: foo
<--- print_function_name
=== call function ===
function name is foo
Hello foo!
=== call function done ===
'''

此時再新增一個調用Decorator的Function,顯示的調用順序爲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def print_function_name(func):
print("---> print_function_name: %s" % func.__name__)
def wrapper():
print("function name is %s " % func.__name__)
return func()
print("<--- print_function_name")
return wrapper
@print_function_name
def foo():
print('Hello foo!')
@print_function_name
def foo2():
print('Hello foo2!')
print("=== call function ===")
foo()
foo2()
print("=== call function done ===")
'''
---> print_function_name: foo
<--- print_function_name
---> print_function_name: foo2
<--- print_function_name
=== call function ===
function name is foo
Hello foo!
function name is foo2
Hello foo2!
=== call function done ===
'''

一個Function使用多個Decorator的調用順序爲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def print_function_name(func):
print("---> print_function_name: %s" % func.__name__)
def wrapper():
print("function name is %s " % func.__name__)
return func()
print("<--- print_function_name")
return wrapper
def print_function_name2(func):
print("---> print_function_name2: %s" % func.__name__)
def wrapper():
print("[2]function name is %s " % func.__name__)
return func()
print("<--- print_function_name2")
return wrapper
@print_function_name
@print_function_name2
def foo():
print('Hello foo!')
print("=== call function ===")
foo()
print("=== call function done ===")
'''
---> print_function_name2: foo
<--- print_function_name2
---> print_function_name: wrapper
<--- print_function_name
=== call function ===
function name is wrapper
[2]function name is foo
Hello foo!
=== call function done ===
'''

上述執行順序大致等於下述寫法

1
print_function_name(print_function_name2(foo()))

Kinds of Decorator

除了上述最簡單的Decorator,下面舉出幾個進階的Decorator

Decorators caller with arguments

  • 當Decorators caller需要參數傳入
  • 將傳入的參數同時附加在返回的Function,達成參數傳遞目的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def print_function_name(func):
def wrapper(*args, **kwargs):
print("function name is %s " % func.__name__)
return func(*args,**kwargs)
return wrapper
@print_function_name
def foo(name):
print('Hello %s!' % name)
foo("Hexo")
'''
function name is foo
Hello Hexo!
'''

Decorators with arguments

  • 當Decorators本身需要參數傳入
  • 需要多一層的Outter function來接收參數傳入
  • 一層一層的返回function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def print_function_name(function_name_prefix):
def decorator(func):
def wrapper():
print("function name is %s_%s"
% (function_name_prefix, func.__name__))
return func()
return wrapper
return decorator
@print_function_name("pre")
def foo():
print('Hello foo!')
foo()
'''
function name is pre_foo
Hello foo!
'''

Decorators on classe functions

  • Python built-in提供了一些關於這類的Class,例如@classmethod,@staticmethod,和@property
  • 第二層function需要接收self參數,故使用args來達成目的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def print_function_name(func):
def wrapper(*args, **kwargs):
print("function name is %s " % func.__name__)
return func(*args, **kwargs)
return wrapper
class DecoratorCaller:
def __init__(self):
pass
@print_function_name
def foo(self):
print('Hello foo!')
decorator_caller = DecoratorCaller()
decorator_caller.foo()
'''
function name is foo
Hello foo!
'''

Classes as decorators

  • 實作一個Class作爲Decorator,並作用於Function
  • Decorator class需要實作__call__ function
  • Decorator caller會在__init__傳入,Decorator需要將它存儲
  • __call__接收*args與**kwargs參數,可以完成caller的參數傳遞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class PrintFunctionName:
def __init__(self, func):
self._func = func
def __call__(self):
print("function name is %s " % self._func.__name__)
return self._func()
@PrintFunctionName
def foo():
print('Hello foo!')
foo()
'''
function name is foo
Hello foo!
'''

Classes as Decorators with Arguments

  • 當實作可接收參數的Decorator class時,Decorator caller本身會在__call__被傳入,與沒有接收參數的Decorator class不同
  • wrapper接收*args與**kwargs參數,可以完成caller的參數傳遞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class PrintFunctionName:
def __init__(self, function_name_prefix):
self._function_name_prefix = function_name_prefix
def __call__(self, func):
def wrapper():
print("function name is %s_%s"
% (self._function_name_prefix, func.__name__))
return func()
return wrapper
@PrintFunctionName("pre")
def foo():
print('Hello foo!')
foo()
'''
function name is pre_foo
Hello foo!
'''