Python 装饰器使用非常地简单。任何会使用 Python 函数的人都可以学习使用装饰器:
1
2
3
|
@
somedecorator
def
some_function
(
)
:
print
(
"Check it out, I'm using decorators!"
)
|
但是如何写装饰器却是另外一回事,而且这并不容易。你必须明白如下这些内容:
- 闭包
- 如何将函数做为一阶参数
- 变量参数
- 参数解包
- 甚至是一些 Python 加载源代码的细节
这些知识都需要花费大量的时间去理解掌握。如果你已经累积了许多其它需要学习的内容。这些内容还值得你去学习吗?
对于我来说,答案是肯定的。希望你的答案也是肯定的。那么,自己写装饰器的好处是什么呢?或者说,它们会让我在日常开发过程中哪些事变得容易?
分析、日志与手段
对于大型应用, 我们常常需要记录应用的状态,以及测量不同活动的数量。通过将这些特别的事件包装到函数或方法中,装饰器可以很轻松地满足这些需求,同时保证代码的可读性。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
from
myapp
.
log
import
logger
def
log_order_event
(
func
)
:
def
wrapper
(
*
args
,
*
*
kwargs
)
:
logger
.
info
(
"Ordering: %s"
,
func
.
__name__
)
order
=
func
(
*
args
,
*
*
kwargs
)
logger
.
debug
(
"Order result: %s"
,
order
.
result
)
return
order
return
wrapper
@
log_order_event
def
order_pizza
(
*
toppings
)
:
# let's get some pizza!
|
这个方法也可以用来记数或者记录其它某些指标。
验证以及运行时检查
Python 是一种强类型语言,但是变量的类型却是动态变化的。虽然这会带来很多好处,但是同时这也意味着更容易引入 bug。对于静态语言,例如 Java, 这些 bug 在编译阶段就可以被发现。因而,你可能希望在对传入或返回的数据进行一些自定义的的检查。装饰器就可以让你非常容易地实现这个需求,并一次性将其应用到多个函数上。
想像一下:你有许多函数,每个函数返回一个字典类型,该字典包含一个“summary ”域。这个域的值不能超过 80 个字符的长度。如果违反这个要求,那就是一个错误。下面这个装饰器会在错误发生时抛出 ValueError 异常:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
def
validate_summary
(
func
)
:
def
wrapper
(
*
args
,
*
*
kwargs
)
:
data
=
func
(
*
args
,
*
*
kwargs
)
if
len
(
data
[
"summary"
]
)
>
80
:
raise
ValueError
(
"Summary too long"
)
return
data
return
wrapper
@
validate_summary
def
fetch_customer_data
(
)
:
# ...
@
validate_summary
def
query_orders
(
criteria
)
:
# ...
@
validate_summary
def
create_invoice
(
params
)
:
# ...
|
创建框架
一旦你掌握了如何写装饰器,你就能够从其使用的简单的语法中获益颇丰,你可以为语言添加新的语义使其使用更加简单。接下来最棒的就是你可以自己扩展 Python 语法。
事实上,很多开源框架都是使用的这样的方式。 Web 应用框架 Flask 就是使用装饰器将不同 URL 路由到不同处理 HTTP 请求函数的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# For a RESTful todo-list API.
@
app
.
route
(
"/tasks/"
,
methods
=
[
"GET"
]
)
def
get_all_tasks
(
)
:
tasks
=
app
.
store
.
get_all_tasks
(
)
return
make_response
(
json
.
dumps
(
tasks
)
,
200
)
@
app
.
route
(
"/tasks/"
,
methods
=
[
"POST"
]
)
def
create_task
(
)
:
payload
=
request
.
get_json
(
force
=
True
)
task_id
=
app
.
store
.
create_task
(
summary
=
payload
[
"summary"
]
,
description
=
payload
[
"description"
]
,
)
task_info
=
{
"id"
:
task_id
}
return
make_response
(
json
.
dumps
(
task_info
)
,
201
)
@
app
.
route
(
"/tasks/<int:task_id>/"
)
def
task_details
(
task_id
)
:
task_info
=
app
.
store
.
task_details
(
task_id
)
if
task_info
is
None
:
return
make_response
(
""
,
404
)
return
json
.
dumps
(
task_info
)
|
这里有一个全局对象 app,此对象有一个 route 方法。此 route 函数返回一个用于修饰请求处理函数的装饰器。这背后的处理是非常复杂的,但是对于使用 Flask 的程序员来说,所有复杂的东西都被隐藏起来了。
在平时使用 Python 过程中,我们也会这样使用装饰器。例如,所有的对象都依赖于类方法与属性装饰器:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class
WeatherSimulation
:
def
__init__
(
self
,
*
*
params
)
:
self
.
params
=
params
@
classmethod
def
for_winter
(
cls
,
*
*
other_params
)
:
params
=
{
'month'
:
'Jan'
,
'temp'
:
'0'
}
params
.
update
(
other_params
)
return
cls
(
*
*
params
)
@
property
def
progress
(
self
)
:
return
self
.
completed_iterations
(
)
/
self
.
total_iterations
(
)
|
这个类有三个不同的 def 语句,但是每一个的语义都是不同的:
- 构造器是一个简单的方法
- for_winter 是一个类方法
- progress 是一个只读的动态属性
@classmethod 装饰器与 @property 装饰器可以让我们在平时使用过程中非常方便地扩展 Python 对象的语义。
复用不能复用的代码
Python 提供了非常强大的工具以将代码包装成易复用的形式,这些工具包括:函数、函数式编程的支持以及一切皆对象的思想。然而,还是存在某些代码并不能通过使用这些工具进行复用。
假设有一个古怪的 API。你可以通过 HTTP 发送 JSON 格式的请求,它 99.9% 的情况下都是正确工作的。但是,小部分请求会返回服务器内部错误的结果。这时候,你需要重新发送请求。在这种情况下,你需要实现重试逻辑,像这样:
1
2
3
4
5
6
7
8
|
resp
=
None
while
True
:
resp
=
make_api_call
(
)
if
resp
.
status_code
==
500
and
tries
<
MAX_TRIES
:
tries
+=
1
continue
break
process_response
(
resp
)
|
现在假设你的代码库中有很都地方都进行调用了函数 make_api_call,那么是不是需要在每个调用的地方都实现这个 loop 循环呢?是不是每次添加一次调用都要实现一遍这个循环呢?这种模式能难有一个样板代码,除非你使用装饰器,那么这就变得非常简单了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# The decorated function returns a Response object,
# which has a status_code attribute. 200 means
# success; 500 indicates a server-side error.
def
retry
(
func
)
:
def
retried_func
(
*
args
,
*
*
kwargs
)
:
MAX_TRIES
=
3
tries
=
0
while
True
:
resp
=
func
(
*
args
,
*
*
kwargs
)
if
resp
.
status_code
==
500
and
tries
<
MAX_TRIES
:
tries
+=
1
continue
break
return
resp
return
retried_func
This
gives
you
an
easy
-
to
-
use
@
retry
decorator
:
@
retry
def
make_api_call
(
)
:
# ....
|
让你的事业腾飞
刚开始写装饰器时可能不是那么容易。虽然这并不像造火箭那么难,但你也需要花费一些时间去学习,掌握其中的奥秘。大部分程序都能够掌握。当你成为团队里面能把装饰器写得很好并且能解决真正的问题的人时,此时其它开发者都会使用你开发的这些装饰器。因为一旦最难的部分,也就是实现装饰器完成后,使用装饰器是非常容易的。这可以极大的放大你所写代码的正面影响,这会让你成为团队的英雄。
我已经培训了成百上千的软件工程师让他们更高效地使用 Python,这些团队一致的反映装饰器是这其中最有价值也是他们在 Python 高阶编程中使用的最重要的工具。这也是为什么其成为了接下来的 Python 课程重点:在 2016 年 5月 25日与 26 日基础在线课程之后。
不管你是以何种方式学习实现装饰器,你都会因其能完成的工作而感到兴奋。毫无疑问,它能永久地改变你使用 Python 的方式 。