|
|
|
|
移动端

3.3.1 装饰器的引入

《自学Python:编程基础、科学计算及数据分析》第3章Python 进阶,在本章中,我们将学习 Python的一些进阶用法,包括函数的进阶,迭代器、生成器、装饰器、上下文管理器的使用,以及 Python中的变量作用域。本节为大家介绍装饰器的引入。

作者:李金来源:机械工业出版社|2018-05-04 13:37

技术沙龙 | 邀您于8月25日与国美/AWS/转转三位专家共同探讨小程序电商实战

3.3 装饰器

3.3.1 装饰器的引入

在 Python中,函数本身就是一个对象:

  1. In [1]: def foo(x):  
  2. ...: print x  
  3. ...:  
  4. In [2]: foo  
  5. Out[2]: <function __main__.foo> 

作为对象,函数有一些自己的方法和属性,这些方法和属性可以用dir()函数查看:

  1. In [3]: dir(foo)  
  2. Out[3]:  
  3. ['__call__',  
  4. '__class__',  
  5. '__closure__',  
  6. '__code__',  
  7. '__defaults__',  
  8. '__delattr__',  
  9. '__dict__',  
  10. '__doc__',  
  11. '__format__',  
  12. '__get__',  
  13. '__getattribute__',  
  14. '__globals__',  
  15. '__hash__',  
  16. '__init__',  
  17. '__module__',  
  18. '__name__',  
  19. '__new__',  
  20. '__reduce__',  
  21. '__reduce_ex__',  
  22. '__repr__',  
  23. '__setattr__',  
  24. '__sizeof__',  
  25. '__str__',  
  26. '__subclasshook__',  
  27. 'func_closure',  
  28. 'func_code',  
  29. 'func_defaults',  
  30. 'func_dict',  
  31. 'func_doc',  
  32. 'func_globals',  
  33. 'func_name'] 

其中.__call__()方法是最重要的一个方法,调用函数 foo(x)相当于调用了对象 foo的.__call__()方法:

  1. In [4]: foo.__call__(42)  
  2. 42 

函数本身可以作为一个参数传给另一个函数:

  1. In [5]: def bar(f, x):  
  2. ...: x += 1  
  3. ...: f(x)  
  4. ...:  
  5. In [6]: bar(foo, 4)  

在介绍装饰器之前,先假设我们定义了这样一个函数add():

  1. In [7]: def add(x, y):  
  2. ...: return x + y  
  3. ...: 

现在我们希望在调用函数的时候,打印一条相关信息,说明哪个函数被调用了。

最简单的做法是在函数中直接加上一条print语句:

  1. def add(x, y):  
  2. print "calling function add"  
  3. return x + y 

不过,除了 add()函数,其他的函数都有这样的需求,在每个函数里都加上一行 print语句显得比较麻烦。

因为功能相似,我们考虑使用一个公共函数,接受一个函数作为参数,并打印出这个函数相关的信息,最后返回这个函数本身。

函数的名字可以通过函数的.__name__属性获得:

  1. In [8]: add.__name__  
  2. Out[8]: 'add' 

利用.__name__属性,我们的公共函数定义如下:

  1. In [9]: def loud(f):  
  2. ...: print "calling function", f.__name__  
  3. ...: return f  
  4. ...: 

调用时,可以用loud(add)(1, 2)代替add(1, 2):

  1. In [10]: loud(add)(1, 2)  
  2. calling function add  
  3. Out[10]: 3 

换一个系统自带函数作为参数,比如 len()函数:

  1. In [11]: loud(len)([1, 2, 3, 4])  
  2. calling function len  
  3. Out[11]: 4 

不过这样的定义方式其实并不完全符合我们的要求。

我们希望在调用函数 add()时打印相关信息,但现在的信息是在调用 loud(add)的时候打印出来的:

  1. In [12]: loud(add)  
  2. calling function add  
  3. Out[12]: <function __main__.add> 

函数add()并没有被调用(没有接受参数),但信息还是被显示了。

为了完成我们想要的功能,可以利用高阶函数的特性,在函数中定义新函数,并将打印信息的功能放到一个内部函数中:

  1. In [13]: def loud_info(f):  
  2. ...: def g(*args, **kwargs):  
  3. ...: print "calling function", f.__name__  
  4. ...: return f(*args, **kwargs)  
  5. ...: return g  
  6. ...: 

如果我们只是调用loud_info(add)而不传入参数,并不会打印相关信息:

  1. In [14]: loud_info(add)  
  2. Out[14]: <function __main__.g> 

传入参数时,打印信息:

  1. In [15]: loud_info(add)(1, 2)  
  2. calling function add  
  3. Out[15]: 3 

在Python中,像loud _info这种为函数添加新特性的函数,一般称为装饰器(Decorator)。

在实际应用中,把函数 f的每个调用都改成 loud_info(f)显得不是很方面。为此, Python提供了“@”符号来简化装饰器的使用。

我们只需要在add()函数的定义前,加上一个@loud_info标志:

  1. In [16]: @loud_info  
  2. ...: def add(x, y):  
  3. ...: return x + y  
  4. ...: 

再调用add()函数,我们会发现装饰器的特性已经被自动加入了:

  1. In [17]: add(2, 3)  
  2. calling function add  
  3. Out[17]: 5 


喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:365934973

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Java Web标签应用开发

Java Web程序员直接在JSP页面中书写Java代码的做法,使得页面中混杂有JavaScript、HTML、Java等多种语言的程序代码,可读性差,可复用性也...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊