前面我们说了,在python中,一切皆对象。函数也是一个对象,而且函数对象可以被赋值给变量,通过变量也能调用该函数。如:

  1. def sayHello(name):
  2. print(name + ' hello')
  3.  
  4. fn = sayHello
  5. fn('roy')

以上代码,输出:

  1. roy hello

函数对象有一个__name__属性,可以拿到函数的名字:

  1. def sayHello(name):
  2. print(name + ' hello')
  3.  
  4. f =sayHello
  5.  
  6. print(f.__name__)
  7. print(sayHello.__name__)

以上代码,输出:

  1. sayHello
  2. sayHello

你会发现,上例中的变量 f 也获得了sayHello函数的功能,而且本质上它就是 sayHello 函数

如果,我想sayHello()这个函数在调用前先打印一句话,你可能会立刻想到,在函数的实现里再加一个print。这当然能达到效果,但是这也修改了函数的定义。

事实上,很多时候我们希望在不修改函数定义的情况下增强函数的功能,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数。让我们先从简单的开始:

  1. def log(fn):
  2. def inner():
  3. print('调用 fn 之前')
  4. fn()
  5.  
  6. return inner
  7.  
  8. def sayHello():
  9. print('say hello')
  10.  
  11. decorated = log(sayHello)
  12. decorated()

以上代码,输出:

  1. 调用 fn 之前
  2. say hello

我们可以说变量 decorated 是 sayHello 的装饰版——即 sayHello加上一些东西。事实上,如果写了一个实用的装饰器,可能会想用装饰版来代替 sayHello,这样就总能得到“附带其他东西”的 sayHello版本。用不着学习任何新的语法,通过将包含函数的变量重新赋值就能轻松做到这一点:

  1. def log(fn):
  2. def inner():
  3. print('调用 fn 之前')
  4. fn()
  5.  
  6. return inner
  7.  
  8. def sayHello():
  9. print('say hello')
  10.  
  11. sayHello = log(sayHello)
  12. sayHello()

以上代码,输出:

  1. 调用 fn 之前
  2. say hello

现在任意调用 sayHello() 都不会得到原来的 sayHello,而是新的装饰器版!明白了吗?

现在这个sayHello函数是不带参数的,那假如带有参数的,又该怎么写呢?看实例:

  1. def log(fn):
  2. def inner(name):
  3. print('调用 fn 之前')
  4. fn(name)
  5.  
  6. return inner
  7.  
  8. def sayHello(name):
  9. print(name, 'say hello')
  10.  
  11. sayHello = log(sayHello)
  12. sayHello('roy')

以上代码,输出:

  1. 调用 fn 之前
  2. roy say hello

在上面代码示例中,用了一个包装的函数来替换包含函数的变量来实现了装饰函数:

  1. sayHello = log(sayHello)
  2. sayHello('roy')

在Python3中通过在函数定义前添加一个装饰器名和 @ 符号,来实现对函数的包装,如:

  1. def log(fn):
  2. def inner(name):
  3. print('调用 fn 之前')
  4. fn(name)
  5.  
  6. return inner
  7.  
  8. @log
  9. def sayHello(name):
  10. print(name, 'say hello')
  11.  
  12. sayHello('roy')

以上代码,输出:

  1. 调用 fn 之前
  2. roy say hello

值得注意的是,这种方式和简单的使用 log 函数的返回值来替换原始变量的做法没有什么不同—— Python 只是添加了一些语法糖来使之看起来更加明确。

上面我们写了一个装饰器,但它是硬编码的,只适用于特定类型的函数——带有1个参数的函数。内部函数 inner 接收1个参数,然后继续将参数传给闭包中的函数。如果我们想要一个能适用任何函数的装饰器呢?这意味着这个装饰器必须接收它所装饰的任何函数的调用信息,并且在调用这些函数时将传递给该装饰器的任何参数都传递给它们,所幸python提供这样的语法,我们来写一个通用的装饰器:

  1. def log(fn):
  2. """
  3. 通用的装饰器
  4. :param fn:
  5. :return:
  6. """
  7. def inner(*args, **kwargs):
  8. print('调用 %s 之前' % fn.__name__)
  9. fn(*args, **kwargs)
  10.  
  11. return inner
  12.  
  13. @log
  14. def sayHello(name):
  15. print(name, 'say hello')
  16.  
  17. @log
  18. def sayGoodbye():
  19. print('say goodbye')
  20.  
  21. @log
  22. def sayMessage(name, message):
  23. print(name, 'say', message)
  24.  
  25. sayHello('roy')
  26. print()
  27.  
  28. sayGoodbye()
  29. print()
  30.  
  31. sayMessage('roy', 'Hello World')

以上代码,输出:

  1. 调用 sayHello 之前
  2. roy say hello
  3.  
  4. 调用 sayGoodbye 之前
  5. say goodbye
  6.  
  7. 调用 sayMessage 之前
  8. roy say Hello World

注:当定义一个函数时,*args 可以表示在调用函数时从迭代器中取出位置参数, 也可以表示在定义函数时接收额外的位置参数。使用 **kwargs 来表示所有未捕获的关键字参数将会被存储在字典 kwargs 中。此前 args 和 kwargs 都不是 Python 中语法的一部分,但在函数定义时使用这两个变量名是一种惯例。和 * 的使用一样,可以在函数调用和定义时使用 **

但,还有个问题,由于log函数是一个Decorator(装饰器),返回一个函数,所以,原来的 sayGoodbye 函数仍然存在,只是现在同名的sayGoodbye变量指向了新的函数,于是调用sayGoodbye()将执行新函数,即在log函数中返回的inner函数。但你去看经过log函数装饰之后的函数,它们的__name__已经从原来的'sayGoodbye'变成了'inner'了:

  1. def log(fn):
  2. """
  3. 通用的装饰器
  4. :param fn:
  5. :return:
  6. """
  7. def inner(*args, **kwargs):
  8. print('调用 %s 之前' % fn.__name__)
  9. fn(*args, **kwargs)
  10.  
  11. return inner
  12.  
  13. @log
  14. def sayGoodbye():
  15. print('say goodbye')
  16.  
  17. print(sayGoodbye.__name__)

以上代码,输出:

  1. inner

因为log函数中返回的那个函数名字就是'inner',所以,需要把原始函数的__name__等属性复制到inner函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写inner.__name__ = fn.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

  1. import functools

  2. def log(fn):
  3. """
  4. 通用的装饰器
  5. :param fn:
  6. :return:
  7. """
  8. @functools.wraps(fn)
  9. def inner(*args, **kwargs):
  10. print('调用 %s 之前' % fn.__name__)
  11. fn(*args, **kwargs)
  12.  
  13. return inner
  14.  
  15. @log
  16. def sayGoodbye():
  17. print('say goodbye')
  18.  
  19. print(sayGoodbye.__name__)

以上代码,输出:

  1. sayGoodbye

如果Decorator(装饰器)本身需要传入参数,那要怎么做呢?那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

  1. import functools
  2.  
  3. def log(msg):
  4.  
  5. def decorator(fn):
  6. """
  7. 通用的装饰器
  8. :param fn:
  9. :return:
  10. """
  11.  
  12. @functools.wraps(fn)
  13. def inner(*args, **kwargs):
  14. print('调用 %s 之前' % fn.__name__)
  15. print('msg:',msg)
  16. fn(*args, **kwargs)
  17.  
  18. return inner
  19.  
  20. return decorator
  21.  
  22. @log('这是一个自定义消息')
  23. def sayGoodbye():
  24. print('say goodbye')
  25.  
  26. sayGoodbye()

以上代码,输出:

  1. 调用 sayGoodbye 之前
  2. msg: 这是一个自定义消息
  3. say goodbye

python3 第二十二章 - 函数式编程之Decorator(装饰器)的更多相关文章

  1. python3 第二十四章 - 函数式编程之Anonymous function(匿名函数)

    匿名函数指一类无须定义标识符的函数或子程序.Python用lambda语法定义匿名函数,只需用表达式而无需申明.lambda语法的定义如下: lambda [arg1 [,arg2, ... argN ...

  2. 《Linux命令行与shell脚本编程大全》 第二十二章 学习笔记

    第二十二章:使用其他shell 什么是dash shell Debian的dash shell是ash shell的直系后代,ash shell是Unix系统上原来地Bourne shell的简化版本 ...

  3. 第二十二章 Django会话与表单验证

    第二十二章 Django会话与表单验证 第一课 模板回顾 1.基本操作 def func(req): return render(req,'index.html',{'val':[1,2,3...]} ...

  4. Gradle 1.12用户指南翻译——第二十二章. 标准的 Gradle 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

  5. “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  6. “全栈2019”Java异常第二十二章:try-with-resources语句详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...

  7. “全栈2019”Java第二十二章:控制流程语句中的决策语句if-else

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  8. python 教程 第二十二章、 其它应用

    第二十二章. 其它应用 1)    Web服务 ##代码 s 000063.SZ ##开盘 o 26.60 ##最高 h 27.05 ##最低 g 26.52 ##最新 l1 26.66 ##涨跌 c ...

  9. 第二十二章 跳出循环-shift参数左移-函数的使用 随堂笔记

    第二十二章 跳出循环-shift参数左移-函数的使用 本节所讲内容: 22.1 跳出循环 22.2 Shift参数左移指令 22.3 函数的使用 22.4 实战-自动备份mysql数据库和nginx服 ...

随机推荐

  1. Hadoop之Hive篇

    想了解Hadoop整体结构及各框架角色建议飞入这篇文章,写的很好:http://www.open-open.com/lib/view/open1385685943484.html .以下文章是本人参考 ...

  2. Java中Optional使用注意事项

    前言 之前遇到过使用Optional之后带来的隐含bug,现在强调记录一下不好的用法,防止错用. Optional不能序列化,不能作为类的字段(field) 这点尤为重要,即类要纯粹.如果是POJO就 ...

  3. SpringMVC handleMapping 处理器映射器 属性清单

    映射器的属性清单 defaultHandler         在映射与所有处理器都不匹配的情况下,指定默认的处理器(处理器即你定义的Controller(action)类) order        ...

  4. RNN的简单的推导演算公式(BPTT)

    附上y=2x-b拟合的简单的代码. import numpy as np x = np.asarray([2,1,3,5,6]); y = np.zeros((1,5)); learning_rate ...

  5. Java入门篇(二)——Java语言基础(上)

    本篇我们开始进入Java的学习,首先在学习如何编写Java语言前要先了解Java程序的基本结构. 一.Java程序的基本结构 一个Java程序的基本结构大体可以分为包.类.main()主方法.标识符. ...

  6. CTF---密码学入门第五题 传统知识+古典密码

    传统知识+古典密码分值:10 来源: 霜羽 难度:易 参与人数:2297人 Get Flag:735人 答题人数:938人 解题通过率:78% 小明某一天收到一封密信,信中写了几个不同的年份     ...

  7. CodeForces798-B. Mike and strings-string中的find()函数

    好久好久好久之前的一个题,今天翻cf,发现这个题没过,补一下. B. Mike and strings time limit per test 2 seconds memory limit per t ...

  8. HDU2973(威尔逊定理)

    YAPTCHA Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total S ...

  9. BZOJ3676: [Apio2014]回文串(回文树)

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3676 这叫模版题TAT #include<cstring> #include< ...

  10. HDU 1232 并查集

    畅通工程                                                                                            Time ...