二十年前,闭包爱上了语法糖,从此Python多了一个叫做装饰器的小伙伴
1. 引子
二十年前,大约也是这个时节,闭包(Closure),一个帅气的小伙子,无可救药地爱上了长相甜美的语法糖(Syntactic Sugar)姑娘。结果呢,后面的故事你们就都知道了,2004年11月30号,Python2.4为他们举行了隆重的婚礼,闭包和语法糖的爱情结晶——小帅哥装饰器作为花童,也参加了他们的婚礼。
2. 闭包
作为一种编程语言特性,闭包得到了很多编程语言的支持,Python也不例外。所谓闭包,在Python中指的是携带一个或多个自由量的函数。闭包函数的自由量不是函数的参数,而是生成这个函数时的环境变量。一旦闭包生成了,自由变量会绑定在函数上,即使离开创造它的环境,自由量依旧有效。总结一下,闭包的概念有以下三个要点。
- 闭包是一个函数
- 闭包函数是由其他代码生成的
- 闭包函数携带了生成环境的信息
我有一篇专门讲闭包的博文,名为《理解Python闭包,这应该是最好的例子》,这里就不再赘述了,只给出一个闭包的例子。
>>> import time
>>> def kids_factor(name): # 定义一个宝宝工厂
t0 = time.time()
def kid(): t1 = time.time() print('我是%s,已经出生%d秒钟了'%(name, int(t1-t0)))
return kid
>>> kid_jack = kids_factor('杰克') # 生产一个名为杰克的宝宝
>>> kid_john = kids_factor('约翰') # 生产一个名为约翰的宝宝
>>> kid_jack() # 让杰克出来走两步,看到杰克已经出生49秒钟了
我是杰克,已经出生49秒钟了
>>> kid_john() # 让约翰出来走两步,看到约翰已经出生15秒钟了
我是约翰,已经出生15秒钟了
>>> kid_john() # 再让约翰出来走两步,看到约翰已经出生22秒钟了
我是约翰,已经出生22秒钟了
>>> kid_jack() # 再让杰克出来走两步,看到杰克已经出生68秒钟了
我是杰克,已经出生68秒钟了
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
通过这个例子可以看到,宝宝(闭包函数)的出生时间t0已经被包含在闭包函数中,让宝宝走两步(运行闭包函数),t1一行中time.time()才被执行。
3. 语法糖
语法糖,听起来有点甜腻,看上去似乎是用“语法”来限定修饰“糖”,其实呢,“糖”只是修饰词,“语法”才是词干。语法糖本质上也是编程语言的一种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。因此,我觉得叫做糖衣语法更合适。
语法糖并不单一指一种语法,是若干糖衣语法的合集。比如,我们经常用到的所谓“三元表达”,就是一种典型的语法糖。
>>> y = 5
>>> x = -1 if y < 0 else 1
>>> x
1
- 1
- 2
- 3
- 4
本文引子闭包爱上的语法糖,则是@这个函数装饰符。函数装饰符和装饰函数必须独占一行,后面紧接被装饰的函数定义。 @函数装饰符将被装饰的函数作为参数传递给装饰函数,最后返回装饰后的同名函数。
4. 装饰器
4.1 装饰函数无参数
通常,装饰函数只接受一个参数,也就是被装饰的函数。这种情况下,无论被装饰的函数有无参数,都可以使用下面例子中的标准写法。
>>> import time
>>> def timer(func):
def wrapper(*args, **kwds): t0 = time.time() func(*args, **kwds) t1 = time.time() print('耗时%0.3f秒'%(t1-t0,))
return wrapper
>>> @timer
def do_something(delay):
print('函数do_something开始')
time.sleep(delay)
print('函数do_something结束')
>>> do_something(3)
函数do_something开始
函数do_something结束
耗时3.086秒
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
4.2 装饰函数有参数
如果装饰器函数本身也需要传递参数,则需要在外层再包裹一层。下面的代码中,装饰器函数timer通过param和**kvs接受可变参数和关键字参数,也就是任意形式的参数;而args和**kwds则是被装饰的函数的参数。
>>> def timer(*param, **kvs):
def wrapper(func):
def _wrapper(*args, **kwds): print('这是装饰器函数的参数:', param, kvs) t0 = time.time() func(*args, **kwds) t1 = time.time() print('耗时%0.3f秒'%(t1-t0,))
return _wrapper
return wrapper
>>> @timer('ok', name='xufive', age=18)
def do_something(delay):
print('函数do_something开始')
time.sleep(delay)
print('函数do_something结束') >>> do_something(3)
这是装饰器函数的参数: ('ok',) {'name': 'xufive', 'age': 18}
函数do_something开始
函数do_something结束
耗时3.068秒
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
有一点需要注意:4.2中,即使装饰函数无参数(此时最好使用4.1的形式),使用装饰器时也必须写作@timer(),而不是@timer。这并不难理解,因为@timer和@timer()不同,前者并没有立即运行timer函数,后者则是立即运行了timer函数,后面的就和4.1形式类似了。
文章来源: xufive.blog.csdn.net,作者:天元浪子,版权归原作者所有,如需转载,请联系作者。
原文链接:xufive.blog.csdn.net/article/details/110184838
- 点赞
- 收藏
- 关注作者
评论(0)