二十年前,闭包爱上了语法糖,从此Python多了一个叫做装饰器的小伙伴

举报
天元浪子 发表于 2021/07/27 00:47:28 2021/07/27
【摘要】 文章目录 1. 引子2. 闭包3. 语法糖4. 装饰器4.1 装饰函数无参数4.2 装饰函数有参数 1. 引子 二十年前,大约也是这个时节,闭包(Closure),一个帅气的小伙子,无可救药地爱上了长相甜美的语法糖(Syntactic Sugar)姑娘。结果呢,后面的故事你们就都知道了,2004年11月30号,Python2.4为他们举行了隆重的婚礼...

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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。