Python 的装饰器模式通过 语法糖 实现,相较于其他语言的装饰器模式,十分的优雅。
下面的例子为Python中主要的装饰器实现事例。
函数装饰器
在 Python 中一切皆对象,是对象就能被装饰,函数也不例外。
不带参数的装饰器
作为装饰器的函数可以带有传入参数,也可以不带传入参数。当不带传入参数时,对应的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 斐波纳兹数列
def Memoization(fn):
cache = dict()
@wraps(fn)
def Wrapper(*args):
result = cache.get(args)
if result is None:
result = fn(*args)
cache[args] = result
return result
return Wrapper
@Memoization
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
|
如上面的例子所示,装饰器函数中不带参数,那么装饰器函数的传入参数即为 被装饰的函数。(有点绕)
简单来说:
- 在被装饰之前,调用的是
fib(100)
。 - 在函数被装饰后
- 调用的是
Memoization(fib)(100)
。 - 由于被装饰了,所以实际上调用的是
Wrapper(100)
。 - 在wrapper内部对计算结果进行了缓存,但实际返回的结果仍为 原始函数 fib 计算的结果。
带参数的装饰器
在带参数的装饰器函数中,传入参数不再是 被装饰的函数。
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
| def AddHtmlTag(tag, *args, **kwargs):
def Decorator(fn):
css_class = "class=\"{}\"".format(kwargs["css_class"]) if "css_class" in kwargs else ""
def Wrapper(*args):
print(fn.__name__)
return "<{tag} {css}>{fn}</{tag}>".format(tag = tag, css = css_class, fn = fn(*args))
return Wrapper
return Decorator
@AddHtmlTag(tag = 'div', css_class = "center")
@AddHtmlTag(tag = 'b', css_class = "black")
def Hello(name):
return "hello world!" + name
print(Hello("tmo"))
# output
Wrapper
Hello
<div class="center"><b class="black">hello world!tmo</b></div>
|
如上面的例子,生成html中的tag标签。在装饰函数中有自己的传入参数,在装饰时需要添加,如:@AddHtmlTag(tag = 'b', css_class = "black")
。
AddHtmlTag函数 返回一个真正的装饰器函数 Decorator,Decorator 函数的传入参数为被装饰的函数。
Decorator 函数返回的又是一个函数 Wrapper,Wrapper 函数才是真正实现功能的部分。
(有点绕)
简单来说:
- 在被装饰之前,调用的是
Hello("tmo")
。 - 在函数被装饰后
- 调用的是
AddHtmlTag(tag = 'b', css_class = "black")(Hello)("tmo")
。 - 其中
AddHtmlTag(tag = 'b', css_class = "black")
返回的是 Decorator
,所以就相当于调用了 Decorator(Hello)("tmp)
。 Decorator(Hello)
被调用后返回了 Wrapper
,于是就相当于调用了 Wrapper("tmo")
。- 随着每一层的递进调用,每一层都添加了原函数没有的功能,最终就编程了另一个函数。
值得注意的一点是,多个装饰器调用的顺序,类似于 入栈出栈,最开始出现的装饰器先进栈,按照 先入后出 的原则,最开始的装饰器在最后才会被执行。
类装饰器
类装饰器 和 函数装饰器 相比,更加的方便了。
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
37
38
| class MyApp:
def __init__(self):
self.__func_map = dict()
def Register(self, name):
def Wrapper(fn):
self.__func_map[name] = fn
return fn
return Wrapper
def Call(self, name):
func = self.__func_map.get(name, None)
if func is not None:
return func()
raise Exception("No func exist. -> {}".format(name))
app = MyApp()
@app.Register("/")
def man_page():
return "This is man page."
@app.Register("/sec")
def sec_page():
return "This is sec page"
print(app.Call("/"))
print(app.Call("/sec"))
# output
This is man page.
This is sec page
|
如上面的例子,模拟了一般的web框架的路由规则注册。
- 首先新建一个类,作为装饰器使用,类中实现 注册功能的函数,以及 调用功能的函数。
- 实例化对象
app = MyApp()
。 - 使用
app.Register
装饰函数,作为注册路由功能。 - 后续只需要执行类似于
app.Call("/")
的命令就能访问对应的函数了。
小结
之前看了用 c++ 以及 Java 对装饰器的实现,只能说,太复杂了!
人生苦短!
参考
Author
Alfons
LastMod
2019-03-09
License
Creative Commons BY-NC-ND 3.0