Python decorator的那些事
1.摘要
Python语言中有一个decorator的语法,中文翻译过来为装饰器。首次接触decorator不免让Python 的学习者理解起来有些困难。本文主要从Python 引入decorator的动机,作用,语法来源以及几个简单的使用例子方面介绍decorator这个概念。希望读完本文后能从根本上理解decorator的本质,在使用的时候能顺手拈来,而不是死记硬背它的语法和用法。
2.Python 引入decorator的动机
Python引入decorator之前函数转换方式(transforming functions and methods)比较难懂并且会使得代码难以理解,所以引入decorator主要是为了简化函数转换的方式。
首先我们看一下什么是函数转换:
例如:
def foo():
print('foo function')
def print_func_run_time(func):
def f():
print('start at %s' % datetime.now())
func()
print('end at %s' % datetime.now())
return f
foo = print_func_run_time(foo)
通过这样一段代码,我们就改变了程序第一行定义的foo函数的函数体内容,每次在调用foo函数的时候,在原来函数功能基础上,还会打印出函数运行的开始和结束时间。
如果print_func_run_time这个函数有很多行话,这么写就会让foo = print_func_run_time(foo)这一行离函数的定义很远,不容易阅读。所以Python为了解决这个问题,就引入了decorator这个语法。现在只要像下面这么写就可以了。
def print_func_run_time(func):
def f():
print('start at %s' % datetime.now())
func()
print('end at %s' % datetime.now())
return f
@print_func_rume_time
def foo()
print('foo funtion')
3.引入decorator的起源
在10th Python Conference DevDay 上, Python的作者Guido在他的keynote演讲中提到Python要支持decorator的语法,后来他说当时只是半开玩笑式地说decorator只是他提出Python几个扩展功能中的一个。
4.Python decorator与设计模式中的decorator
熟悉decorator设计模式的人,乍一看可能会以为Python中的decorator与设计模式Gang of Four中的decorator是一样的,但其实并不一致。这也是Python decorator让人迷惑不解的一个主要原因。decorator这个名字起的并不好,很多人都抱怨过这个名字。
5.decorator的语法
@dec2
@dec1
def func(arg1, arg2, ...):
pass
根据前面的函数转换,这几行代码等价于下面的代码:
def func(arg1, arg2, ...):
pass
func = dec2(dec1(func))
5.1多个decorator的顺序问题
多个decorator的执行顺序是自底向上,但是很多人都搞不清楚这个顺序。如果看了上面那个例子,这个问题很容易理解。另外在我们可以从数学上来理解这个问题,我们初高中时都学过函数传递,如:y = g(f(x)),我相信学过初中数学的人都能理解这个方程的含义:先算f(x)的结果r,把r作为g函数的参数,最后算出y。这么一类比,上例中的多个decorator其实就是这样写的,func = dec2(dec1(func))。所以我们就知道要先执行dec1,再执行dec2。
5.2 decorator函数的定义
为什么decorator函数要以一个函数作为参数,而且内部还要定义一个函数,最后还要返回一个函数?在我们理解decorator的本质后,这个问题就迎刃而解了。我们还以print_func_run_time为例。
def print_func_run_time(func):
def f():
print('start at %s' % datetime.now())
func()
print('end at %s' % datetime.now())
return f
@print_func_rume_time
def foo()
print('foo funtion')
这段代码等价于:
foo = print_func_run_time(foo)
首先,foo是一个函数,foo作为入参传递给print_func_run_time,而后者又返回一个新的函数赋值给foo。从上面这行等价的代码倒推,我们也就理解了为什么入参是一个函数,返回值也是一个函数这两个问题。
那为什么print_func_run_time中还要定义一个函数呢?由于函数入参是局部变量,如果不在print_func_run_time中定义f函数,那么就无法使用入参这个局部变量。其中定义的f函数一定要调用foo函数,才能完成原来foo函数最初实现的功能,然后在f函数中再增加一些额外的功能,最后再返回。实际上decorator函数中定义的内部函数更是一个wrapper函数。
6.decorator为什么是现在这种语法形式?
在最初Python要引入decorator这个语法时候,社区曾经有好几种decorator的语法定义形式。
例如:
-
decorator与函数定义放到一行:
def @classmethod foo(arg1,arg2): pass def @accepts(int,int),@returns(float) bar(low,high): pass def foo @classmethod (arg1,arg2): pass def bar @accepts(int,int),@returns(float) (low,high): pass
-
decorator放到函数定义的下面
def foo(arg1,arg2): @classmethod pass def bar(low,high): @accepts(int,int) @returns(float) pass
-
以及用decorate这个关键字来定义
decorate: classmethod def foo(arg1,arg2): pass decorate: accepts(int,int) returns(float) def bar(low,high): pass
那么最后为什么采用了现在这种语法形式呢?
因为Python的作者Guido更喜欢这种做法,所以决定采用这种将@decorator放到def 前面的做法。同时如果一个函数有很多行decorator,那么当你读到函数代码的时候还可能会有一种将decorator‘隐藏’起来的感觉。
7.例子
打印函数的名字:
def print_func_name(func):
def f(*args, **kwargs):
print('call func %s' % func.__name__)
return func(*args, **kwargs)
return f
@print_func_name
def add(a, b):
return a + b
@print_func_name
def sub(a, b):
return a - b
print(add(1, 2))
print(sub(1, 2))
输出结果:
call func add
3
call func sub
-1
总结
Python decorator本质就是函数转换(transforming functions and method,为了理解它我们可以对比数学上函数传递y = g(f(x))的概念。同时我们也可以看到很多知识也有其不合理和容易混淆地方以及为什么是这种形式,这很可能就是他的设计者当时的第一感觉,这也是很有意思的地方。
参考文献
PEP 318 – Decorators for Functions and Methods https://www.python.org/dev/peps/pep-0318/
Design Patterns https://en.wikipedia.org/wiki/Design_Patterns
下一篇: python日期格式问题datetime