python函数修饰器
python函数修饰器
什么是修饰器?
修饰器是一个函数,接受一个函数或方法作为其唯一的参数,并返回一个新函数或方法,其中整合了修饰后的函数或方法,并附带了一些额外的功能.1
上面的定义不免有点难以理解,我们来看下面的图
我们之前所理解的python执行函数过程是如图1.1的流程.
如果我们给函数添加了修饰器,那么当程序执行到函数A的时候,系统会检测到函数A上有一个修饰器,那么系统就会先执行修饰器里的过程然后再回到函数执行函数的内容.
但是从修饰器回来的那一条线路其实并不是必须的, 需要在修饰器里面编写回来的语句回到函数,接下来会讲到
修饰器的简单作用
在其定义中就已经介绍修饰器的作用是给函数增加额外的功能.
注: 在修饰器的简单作用这一部分,接下来的内容我无法自己组织语言将其讲清楚,故参考了简书作者MrYun 谈谈python修饰器 的内容,个人觉得这一篇在引入修饰器作用方面的描写很棒!
从简单的一个例子讲起,现在我们有一个函数
def foo(): print("this is a test") foo()
现在我们需要给它进行性能测试,那么需要改成以下内容
import time def foo(): start = time.clock() print("this is a test") end = time.clock() print("start:", start, " end:", end) foo()
如果我们希望给几十上百个函数都添加这样的性能测试,那么需要在每个函数内部开头与结尾都这样编辑吗?显然不是.
这个时候我们编写一个新的函数test(),在test的函数开头与结尾编写时间定义,将要测试的函数传入test的函数(函数也是一个变量,是可以作为参数的),在中间执行,比如以下内容
import time def foo(): print("this is a test") def test(func): start = time.clock() foo() end = time.clock() print("start:", start, " end:", end) test(foo)
现在我们就可以给每个函数进行测试了
for 函数 in 项目: test(函数)
如果我们将test中的输出加入到文件中,我们就可以得到每个函数的性能记录
但是, 现在我们需要给大量函数实现另一个功能: 日志功能, 也就是在项目执行过程中, 函数的每一个操作都被记录下来, 意味着每使用一次函数都要手动编写test(foo), 尤其是如果需要使用函数的返回值的时候, 这种方式就有点捉襟见肘了
这个时候修饰器的作用就显示出来了. 它可以在每个使用它的函数上进行功能的添加, 而且使用者完全感受不到他的存在, 也就是说我们使用的时候依然是foo(), 但是在内部项目却另外实现了test()的功能
这个修饰器的使用格式如下,具体内容之后会讲解
import time def test(func): def wrapper(): start = time.clock() func() end = time.clock() print("start:", start, " end:", end) return wrapper @test def foo(): print("this is a test") foo()
修饰器的使用
上面已经了解到了修饰器的作用,那么我们可以了解修饰器的格式了.
上面插入的修饰器太过突兀,我们来段过渡代码(这一段代码也是来自那篇简书)
import time def test(func): def wrapper(): start = time.clock() func() end = time.clock() print("start:", start, " end:", end) return wrapper def foo(): print("this is a test") foo = test(foo) foo()
执行过程可以这样理解:
foo = test(foo)
foo() = test(foo)() = wrapper()
在python中, 我们把foo = test(foo)用一种更简单的形式来表达, 就是在使用装饰器的函数foo上面加上
@修饰器名称, python的修饰器是一种语法糖.
@test def foo(): print("this is a test")
如果需要使用到foo函数的返回值,那么test函数可以这样写
import time def test(func): def wrapper(): start = time.clock() print("this is a order test, if you need not it, delete it") # 用于测试执行顺序,可以跟着走一遍 a = func() end = time.clock() print("start:", start, " end:", end) return a # 这种获得返回值的方法可能在多层修饰器的时候有矛盾,我先用!!!标记, 等理顺后再回来修改,如果我发布之后这里依然存在...说明我忘记了... return wrapper @test def foo(): print("this is a test")return "this is a return value" print(foo()) # 输出 # this is a test wrapper, if you need not it, delete it # this is a test # start: 4.44444839506524e-07 end: 1.8222238419767486e-05 # this is a return value
在《Python3程序开发指南第二版》(以下简称《指南》)中给的例子是一个对python初学者(没涉及项目)来说比较有趣的小修饰器, 有兴趣可以看看,我给它做了一点注释
def positive_result(function): def wrapper(*args, **kwargs): # result获得函数的返回值, 进行结果判断 result = function(*args, **kwargs) # assert断言, 如果function函数的返回值大于等于0, 的产生一个AssertionError异常 assert result >= 0, function.__name__ + "() result isn't >= 0" # 返回 return result # 将wrapper的docstring和名称设置成和原始函数一样,有利于内省(获得自身的信息) wrapper.__name__ = function.__name__ wrapper.__doc__ = function.__doc__ return wrapper # 使用positive_result修饰器 @positive_result def discriminant(a,b,c): return (b**2) - (4*a*c) print(discriminant(0,6,5)) print(discriminant(2,6,5))
执行过程可以这样理解:
discriminant = positive_result(discriminant) discriminant(a,b,c) = positive_result(discriminant)(a,b,c) = wrapper(a, b, c)
《指南》中给出了这个例子的简化版, 使用到了functools.wraps(function)
def positive_result(function): # wrapper本身使用functools模块的@functools.wraps进行包裹, 这可以确保wrapper的名称与docstring与function相同 @functools.wraps(function) def wrapper(*args, **kwargs): result = function(*args, **kwargs) assert result >= 0, function.__name__ + "() result isn't >= 0" return result return wrapper
修饰器参数化
现在我们已经了解到了什么是修饰器以及修饰器的基本使用, 那么在上面的日志修饰器上, 我们的日志信息往往是要写入文件内,但是不同的函数需要写进的文件名不一样, 那么简单的
@修饰器名称已经没法满足需要了, 这个时候就需要修饰器参数化, 即将要操作的文件名传递给test()函数
现在放一个《指南》中给出的例子
import functools def bounded(mininum, maxinum): def decorator(function): @functools.wraps(function) def wrapper(*args, **kwargs): result = function(*args, **kwargs) if result < mininum: return mininum elif result > maxinum: return maxinum return result return wrapper return decorator @bounded(0,100) def percent(amount, total): return (amount / total) * 100 percent(15,100)
-
执行过程如下
执行过程可以这样理解percent = bounded(mininum, maxinum)(percent) percent(amount, total) = bounded(mininum, maxinum)(percent)(amount, total) = wrapper(amount, total)
与普通装饰器的对比:
《Python3程序开发指南第二版》第八章P311 ↩︎
- 点赞 2
- 收藏
- 分享
- 文章举报
- cenos上面python3及mysql的安装
- 《Python3程序开发指南(第二版)》例题之多线程文件查找关键词
- python导入模块
- python实现用户好友推荐
- python基于item-item filtering实现话题推荐
- 用python爬取高考网历年高考分数线将数据放入MySQL并绘制图表
- 软件测试基础+测试开发+python+测试工具免费领取
- python自学中的我
- ZZULI 1015: 计算时间间隔 Python
- python起步——可变对象和不可变对象
- Gvim —— win 7 下 vim 环境配置 及python开发常用设置
- 使用python脚本提取OC中写死的字符串方便国际化
- python3利用pandas将csv文件中的数据导入mysql数据库
- 【Python基础操作】1.数据读取(csv,Excel,MySQL)
- Python3.6.2 图形界面模块Tk (Day1)
- 用python+tkinter写个校验和工具
- python selenium+pywin32 实现网页另存为
- python 输出所有大小写字母和0~9数字
- pycharm导入python包
- python神器pycharm的安装