Ⅰ python装饰器是什么意思
装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python面试中必问的问题,但对于好多小白来讲,这个功能 有点绕,自学时直接绕过去了,然后面试问到了就挂了,因为装饰器是程序开发的基础知识,这个都 不会,别跟人家说你会Python, 看了下面的文章,保证你学会装饰器。
1、先明白这段代码
####第一波####
deffoo():
print'foo'
foo#表示是函数
foo()#表示执行foo函数
####第二波####
deffoo():
print'foo'
foo=lambdax:x+1
foo()#执行下面的lambda表达式,而不再是原来的foo函数,因为函数foo被重新定义了
2、需求来了
初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:
###############基础平台提供的功能如下###############
deff1():
print'f1'
deff2():
print'f2'
deff3():
print'f3'
deff4():
print'f4'
###############业务部门A调用基础平台提供的功能###############
f1()
f2()
f3()
f4()
###############业务部门B调用基础平台提供的功能###############
f1()
f2()
f3()
f4()
目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。
老大把工作交给 Low B,他是这么做的:
跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。诶,这样一来基础平台就不需要做任何修改了。
当天Low B 被开除了…
老大把工作交给 Low BB,他是这么做的:
###############基础平台提供的功能如下###############
deff1():
#验证1
#验证2
#验证3
print'f1'
deff2():
#验证1
#验证2
#验证3
print'f2'
deff3():
#验证1
#验证2
#验证3
print'f3'
deff4():
#验证1
#验证2
#验证3
print'f4'
###############业务部门不变###############
###业务部门A调用基础平台提供的功能###
f1()
f2()
f3()
f4()
###业务部门B调用基础平台提供的功能###
f1()
f2()
f3()
f4()
过了一周 Low BB 被开除了…
老大把工作交给 Low BBB,他是这么做的:
只对基础平台的代码进行重构,其他业务部门无需做任何修改
###############基础平台提供的功能如下###############
defcheck_login():
#验证1
#验证2
#验证3
pass
deff1():
check_login()
print'f1'
deff2():
check_login()
print'f2'
deff3():
check_login()
print'f3'
deff4():
check_login()
print'f4'
老大看了下Low BBB 的实现,嘴角漏出了一丝的欣慰的笑,语重心长的跟Low BBB聊了个天:
老大说:
写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
封闭:已实现的功能代码块
开放:对扩展开发
如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3、f4的内部进行修改代码,老板就给了Low BBB一个实现方案:
defw1(func):
definner():
#验证1
#验证2
#验证3
returnfunc()
returninner
@w1
deff1():
print'f1'
@w1
deff2():
print'f2'
@w1
deff3():
print'f3'
@w1
deff4():
print'f4'
对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1 f2 f3 f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作。
Low BBB心惊胆战的问了下,这段代码的内部执行原理是什么呢?
老大正要生气,突然Low BBB的手机掉到地上,恰恰屏保就是Low BBB的女友照片,老大一看一紧一抖,喜笑颜开,交定了Low BBB这个朋友。详细的开始讲解了:
单独以f1为例:
defw1(func):
definner():
#验证1
#验证2
#验证3
returnfunc()
returninner
@w1
deff1():
print'f1'
当写完这段代码后(函数未被执行、未被执行、未被执行),python解释器就会从上到下解释代码,步骤如下:
def w1(func): ==>将w1函数加载到内存
@w1
没错,从表面上看解释器仅仅会解释这两句代码,因为函数在没有被调用之前其内部代码不会被执行。
从表面上看解释器着实会执行这两句,但是 @w1 这一句代码里却有大文章,@函数名是python的一种语法糖。
如上例@w1内部会执行一下操作:
执行w1函数,并将 @w1 下面的函数作为w1函数的参数,即:@w1 等价于 w1(f1)
所以,内部就会去执行:
def inner:
#验证
return f1() # func是参数,此时 func 等于 f1
return inner # 返回的 inner,inner代表的是函数,非执行函数
其实就是将原来的 f1 函数塞进另外一个函数中
将执行完的 w1 函数返回值赋值给@w1下面的函数的函数名
w1函数的返回值是:
def inner:
#验证
return 原来f1() # 此处的 f1 表示原来的f1函数
然后,将此返回值再重新赋值给 f1,即:
新f1 =def inner:
#验证
return 原来f1()
所以,以后业务部门想要执行 f1 函数时,就会执行 新f1 函数,在 新f1 函数内部先执行验证,再执行原来的f1函数,然后将 原来f1 函数的返回值 返回给了业务调用者。
如此一来, 即执行了验证的功能,又执行了原来f1函数的内容,并将原f1函数返回值 返回给业务调用着
Low BBB 你明白了吗?要是没明白的话,我晚上去你家帮你解决吧!!!
先把上述流程看懂,之后还会继续更新…
3、问答时间
问题:被装饰的函数如果有参数呢?
#一个参数
defw1(func):
definner(arg):
#验证1
#验证2
#验证3
returnfunc(arg)
returninner
@w1
deff1(arg):
print'f1'
#两个参数
defw1(func):
definner(arg1,arg2):
#验证1
#验证2
#验证3
returnfunc(arg1,arg2)
returninner
@w1
deff1(arg1,arg2):
print'f1'
#三个参数
defw1(func):
definner(arg1,arg2,arg3):
#验证1
#验证2
#验证3
returnfunc(arg1,arg2,arg3)
returninner
@w1
deff1(arg1,arg2,arg3):
print'f1'
问题:可以装饰具有处理n个参数的函数的装饰器?
defw1(func):
definner(*args,**kwargs):
#验证1
#验证2
#验证3
returnfunc(*args,**kwargs)
returninner
@w1
deff1(arg1,arg2,arg3):
print'f1'
问题:一个函数可以被多个装饰器装饰吗?
defw1(func):
definner(*args,**kwargs):
#验证1
#验证2
#验证3
returnfunc(*args,**kwargs)
returninner
defw2(func):
definner(*args,**kwargs):
#验证1
#验证2
#验证3
returnfunc(*args,**kwargs)
returninner
@w1
@w2
deff1(arg1,arg2,arg3):
print'f1'
问题:还有什么更吊的装饰器吗?
#!/usr/bin/envpython
#coding:utf-8
defBefore(request,kargs):
print'before'
defAfter(request,kargs):
print'after'
defFilter(before_func,after_func):
defouter(main_func):
defwrapper(request,kargs):
before_result=before_func(request,kargs)
if(before_result!=None):
returnbefore_result;
main_result=main_func(request,kargs)
if(main_result!=None):
returnmain_result;
after_result=after_func(request,kargs)
if(after_result!=None):
returnafter_result;
returnwrapper
returnouter
@Filter(Before,After)
defIndex(request,kargs):
print'index'
Ⅱ Python装饰器是怎么实现的
简单来讲,可以不严谨地把Python的装饰器看做一个包装函数的函数。 比如,有一个函数: def func(): print 'func() run.' if '__main__' == __name__: func() 运行后将输出: func() run. 现在需要在函数运行前后打印一条日志
Ⅲ python装饰器的作用和功能
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能
Ⅳ python装饰器有什么用
先来个形象比方
内裤可以用来遮羞,但是到了冬天它没法为我们防风御寒,聪明的人们发明了长裤,有了长裤后宝宝再也不冷了,装饰器就像我们这里说的长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效。
再回到我们的主题
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
先来看一个简单例子:
def foo():
print('i am foo')
现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:
def foo():
print('i am foo')
logging.info("foo is running")
bar()、bar2()也有类似的需求,怎么做?再写一个logging在bar函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码
def use_logging(func):
logging.warn("%s is running" % func.__name__)
func()def bar():
print('i am bar')use_logging(bar)
逻辑上不难理解,
但是这样的话,我们每次都要将一个函数作为参数传递给use_logging函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行bar(),但是现在不得不改成use_logging(bar)。那么有没有更好的方式的呢?当然有,答案就是装饰器。
简单装饰器
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapperdef bar():
print('i am bar')bar = use_logging(bar)bar()
函数use_logging就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像bar被use_logging装饰了。在这个例子中,函数进入和退出时
,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。
@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper@use_loggingdef foo():
print("i am foo")@use_loggingdef bar():
print("i am bar")bar()
如上所示,这样我们就可以省去bar =
use_logging(bar)这一句了,直接调用bar()即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。
装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。
带参数的装饰器
装饰器还有更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,比如@use_logging,该装饰器唯一的参数就是执行业务的函数。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator@use_logging(level="warn")def foo(name='foo'):
print("i am %s" % name)foo()
上面的use_logging是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我
们使用@use_logging(level="warn")调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。
类装饰器
再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的\_\_call\_\_方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:
装饰器
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
函数
@loggeddef f(x):
"""does some math"""
return x + x * x
该函数完成等价于:
def f(x):
"""does some math"""
return x + x * xf = logged(f)
不难发现,函数f被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。
print f.__name__ # prints 'with_logging'print f.__doc__ # prints None
这个问题就比较严重的,好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。
from functools import wrapsdef logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging@loggeddef f(x):
"""does some math"""
return x + x * xprint f.__name__ # prints 'f'print f.__doc__ # prints 'does some math'
内置装饰器
@staticmathod、@classmethod、@property
装饰器的顺序
@a@b@cdef f ():
等效于
f = a(b(c(f)))
Ⅳ python 装饰器原理
官方文档如下:
Python 2.2 extended Python's object model by adding static methods and class methods, but it didn't extend Python's syntax to provide any new way of defining static or class methods. Instead, you had to write a statement in the usual way, and pass the resulting method to a or function that would wrap up the function as a method of the new type.
Python
Ⅵ Python中的装饰器是作什么用的在哪可以找到这些教程。
装饰器是Python语法糖的一种,可以用来简化代码,让代码更加简洁
装饰器的作用是在不改变函数代码和调用方式的前提下,为已有函数功能添加额外的功能。
可以通过装饰器对代码实现权限管理,用户验证,日志管理,缓存判断,参数检查等等。
以上内容均来自传智播客,自己可以去搜搜,很多课。
Ⅶ 学习Python需要哪些准备
小蜗这里整理了一份Python全栈开发系统的学习路线,每个阶段所要掌握的知识都已列出,题主可参考这份大纲来进行学习规划;
第一阶段:专业核心基础
阶段目标:
1. 熟练掌握Python的开发环境与编程核心知识
2. 熟练运用Python面向对象知识进行程序开发
3. 对Python的核心库和组件有深入理解
4. 熟练应用SQL语句进行数据库常用操作
5. 熟练运用Linux操作系统命令及环境配置
6. 熟练使用MySQL,掌握数据库高级操作
7. 能综合运用所学知识完成项目
知识点:
Python编程基础、Python面向对象、Python高级进阶、MySQL数据库、Linux操作系统。
1、Python编程基础,语法规则,函数与参数,数据类型,模块与包,文件IO,培养扎实的Python编程基本功,同时对Python核心对象和库的编程有熟练的运用。
2、Python面向对象,核心对象,异常处理,多线程,网络编程,深入理解面向对象编程,异常处理机制,多线程原理,网络协议知识,并熟练运用于项目中。
3、类的原理,MetaClass,下划线的特殊方法,递归,魔术方法,反射,迭代器,装饰器,UnitTest,Mock。深入理解面向对象底层原理,掌握Python开发高级进阶技术,理解单元测试技术。
4、数据库知识,范式,MySQL配置,命令,建库建表,数据的增删改查,约束,视图,存储过程,函数,触发器,事务,游标,PDBC,深入理解数据库管理系统通用知识及MySQL数据库的使用与管理。为Python后台开发打下坚实基础。
5、Linux安装配置,文件目录操作,VI命令,管理,用户与权限,环境配置,Docker,Shell编程Linux作为一个主流的服务器操作系统,是每一个开发工程师必须掌握的重点技术,并且能够熟练运用。
第二阶段:PythonWEB开发
阶段目标:
1. 熟练掌握Web前端开发技术,HTML,CSS,JavaScript及前端框架
2. 深入理解Web系统中的前后端交互过程与通信协议
3. 熟练运用Web前端和Django和Flask等主流框架完成Web系统开发
4. 深入理解网络协议,分布式,PDBC,AJAX,JSON等知识
5. 能够运用所学知识开发一个MiniWeb框架,掌握框架实现原理
6. 使用Web开发框架实现贯穿项目
知识点:
Web前端编程、Web前端高级、Django开发框架、Flask开发框架、Web开发项目实战。
1、Web页面元素,布局,CSS样式,盒模型,JavaScript,JQuery与Bootstrap掌握前端开发技术,掌握JQuery与BootStrap前端开发框架,完成页面布局与美化。
2、前端开发框架Vue,JSON数据,网络通信协议,Web服务器与前端交互熟练使用Vue框架,深入理解HTTP网络协议,熟练使用Swagger,AJAX技术实现前后端交互。
3、自定义Web开发框架,Django框架的基本使用,Model属性及后端配置,Cookie与Session,模板Templates,ORM数据模型,Redis二级缓存,RESTful,MVC模型掌握Django框架常用API,整合前端技术,开发完整的WEB系统和框架。
4、Flask安装配置,App对象的初始化和配置,视图函数的路由,Request对象,Abort函数,自定义错误,视图函数的返回值,Flask上下文和请求钩子,模板,数据库扩展包Flask-Sqlalchemy,数据库迁移扩展包Flask-Migrate,邮件扩展包Flask-Mail。掌握Flask框架的常用API,与Django框架的异同,并能独立开发完整的WEB系统开发。
第三阶段:爬虫与数据分析
阶段目标:
1. 熟练掌握爬虫运行原理及常见网络抓包工具使用,能够对HTTP及HTTPS协议进行抓包分析
2. 熟练掌握各种常见的网页结构解析库对抓取结果进行解析和提取
3. 熟练掌握各种常见反爬机制及应对策略,能够针对常见的反爬措施进行处理
4. 熟练使用商业爬虫框架Scrapy编写大型网络爬虫进行分布式内容爬取
5. 熟练掌握数据分析相关概念及工作流程
6. 熟练掌握主流数据分析工具Numpy、Pandas和Matplotlib的使用
7. 熟练掌握数据清洗、整理、格式转换、数据分析报告编写
8. 能够综合利用爬虫爬取豆瓣网电影评论数据并完成数据分析全流程项目实战
知识点:
网络爬虫开发、数据分析之Numpy、数据分析之Pandas。
1、爬虫页面爬取原理、爬取流程、页面解析工具LXML,Beautifulfoup,正则表达式,代理池编写和架构、常见反爬措施及解决方案、爬虫框架结构、商业爬虫框架Scrapy,基于对爬虫爬取原理、网站数据爬取流程及网络协议的分析和了解,掌握网页解析工具的使用,能够灵活应对大部分网站的反爬策略,具备独立完成爬虫框架的编写能力和熟练应用大型商业爬虫框架编写分布式爬虫的能力。
2、Numpy中的ndarray数据结构特点、numpy所支持的数据类型、自带的数组创建方法、算术运算符、矩阵积、自增和自减、通用函数和聚合函数、切片索引、ndarray的向量化和广播机制,熟悉数据分析三大利器之一Numpy的常见使用,熟悉ndarray数据结构的特点和常见操作,掌握针对不同维度的ndarray数组的分片、索引、矩阵运算等操作。
3、Pandas里面的三大数据结构,包括Dataframe、Series和Index对象的基本概念和使用,索引对象的更换及删除索引、算术和数据对齐方法,数据清洗和数据规整、结构转换,熟悉数据分析三大利器之一Pandas的常见使用,熟悉Pandas中三大数据对象的使用方法,能够使用Pandas完成数据分析中最重要的数据清洗、格式转换和数据规整工作、Pandas对文件的读取和操作方法。
4、matplotlib三层结构体系、各种常见图表类型折线图、柱状图、堆积柱状图、饼图的绘制、图例、文本、标线的添加、可视化文件的保存,熟悉数据分析三大利器之一Matplotlib的常见使用,熟悉Matplotlib的三层结构,能够熟练使用Matplotlib绘制各种常见的数据分析图表。能够综合利用课程中所讲的各种数据分析和可视化工具完成股票市场数据分析和预测、共享单车用户群里数据分析、全球幸福指数数据分析等项目的全程实战。
第四阶段:机器学习与人工智能
阶段目标:
1. 理解机器学习相关的基本概念及系统处理流程
2. 能够熟练应用各种常见的机器学习模型解决监督学习和非监督学习训练和测试问题,解决回归、分类问题
3. 熟练掌握常见的分类算法和回归算法模型,如KNN、决策树、随机森林、K-Means等
4. 掌握卷积神经网络对图像识别、自然语言识别问题的处理方式,熟悉深度学习框架TF里面的张量、会话、梯度优化模型等
5. 掌握深度学习卷积神经网络运行机制,能够自定义卷积层、池化层、FC层完成图像识别、手写字体识别、验证码识别等常规深度学习实战项目
知识点:
1、机器学习常见算法、sklearn数据集的使用、字典特征抽取、文本特征抽取、归一化、标准化、数据主成分分析PCA、KNN算法、决策树模型、随机森林、线性回归及逻辑回归模型和算法。熟悉机器学习相关基础概念,熟练掌握机器学习基本工作流程,熟悉特征工程、能够使用各种常见机器学习算法模型解决分类、回归、聚类等问题。
2、Tensorflow相关的基本概念,TF数据流图、会话、张量、tensorboard可视化、张量修改、TF文件读取、tensorflow playround使用、神经网络结构、卷积计算、激活函数计算、池化层设计,掌握机器学习和深度学习之前的区别和练习,熟练掌握深度学习基本工作流程,熟练掌握神经网络的结构层次及特点,掌握张量、图结构、OP对象等的使用,熟悉输入层、卷积层、池化层和全连接层的设计,完成验证码识别、图像识别、手写输入识别等常见深度学习项目全程实战。
Ⅷ 关于python装饰器的问题
装饰器函数参数要传函数,而不是字符串。
装饰器函数特点:
1,参数为函数对象
2,使用内部函数
3,返回函数对象
在你的代码中:
装饰器函数是arg_func(sex)
内部函数是func1()
被装饰函数是man()和woman()
所以代码要改成:
defarg_func(sex):
deffunc1():
sex()
if(sex.__name__=='man'):
print("youcan't")
if(sex.__name__=='woman'):
print("youcan")
returnfunc1
@arg_func
defman():
print('goodgoodstudy')
@arg_func
defwoman():
print('goodgoodstudy')
man()
woman()
PS:装饰器就是为了简化代码,增加可读性,方便团队开发,在不修改原函数代码的前提下,通过封装修改功能,而@修饰就是为了通过原函数名调用时,不直接执行原函数,而是把原函数传递到装饰器函数,通过内部函数(闭包)来调用原函数。这样好处,就是统一调用方式。
Ⅸ 什么是Python装饰器
所谓装饰器就是把函数包装一下,为函数添加一些附加功能,装饰器就是一个函数,参数为被包装的函数,返回包装后的函数:你可以试下:
defd(fp):
def_d(*arg,**karg):
print"dosthbeforefp.."
r=fp(*arg,**karg)
print"dosthafterfp.."
returnr
return_d
@d
deff():
print"callf"
#上面使用@d来表示装饰器和下面是一个意思
#f=d(f)
f()#调用f
Ⅹ 如何理解Python装饰器
理解Python中的装饰器
@makebold
@makeitalic
def say():
return "Hello"
打印出如下的输出:
<b><i>Hello<i></b>
你会怎么做?最后给出的答案是:
def makebold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makebold
@makeitalic
def hello():
return "hello world"
print hello() ## 返回 <b><i>hello world</i></b>
现在我们来看看如何从一些最基础的方式来理解Python的装饰器。英文讨论参考Here。
装饰器是一个很着名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
1.1. 需求是怎么来的?
装饰器的定义很是抽象,我们来看一个小例子。
def foo():
print 'in foo()'
foo()
这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:
import time
def foo():
start = time.clock()
print 'in foo()'
end = time.clock()
print 'used:', end - start
foo()
很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。
怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?
1.2. 以不变应万变,是变也
还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!
import time
def foo():
print 'in foo()'
def timeit(func):
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
timeit(foo)
看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。
1.3. 最大限度地少改动!
既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!
#-*- coding: UTF-8 -*-
import time
def foo():
print 'in foo()'
# 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法
def timeit(func):
# 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
# 将包装后的函数返回
return wrapper
foo = timeit(foo)
foo()
这样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。
这个例子仅用于演示,并没有考虑foo带有参数和有返回值的情况,完善它的重任就交给你了 :)
上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。
import time
def timeit(func):
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
return wrapper
@timeit
def foo():
print 'in foo()'
foo()
重点关注第11行的@timeit,在定义上加上这一行与另外写foo = timeit(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。
-------------------
要理解python的装饰器,我们首先必须明白在Python中函数也是被视为对象。这一点很重要。先看一个例子:
def shout(word="yes") :
return word.capitalize()+" !"
print shout()
# 输出 : 'Yes !'
# 作为一个对象,你可以把函数赋给任何其他对象变量
scream = shout
# 注意我们没有使用圆括号,因为我们不是在调用函数
# 我们把函数shout赋给scream,也就是说你可以通过scream调用shout
print scream()
# 输出 : 'Yes !'
# 还有,你可以删除旧的名字shout,但是你仍然可以通过scream来访问该函数
del shout
try :
print shout()
except NameError, e :
print e
#输出 : "name 'shout' is not defined"
print scream()
# 输出 : 'Yes !'
我们暂且把这个话题放旁边,我们先看看python另外一个很有意思的属性:可以在函数中定义函数:
def talk() :
# 你可以在talk中定义另外一个函数
def whisper(word="yes") :
return word.lower()+"...";
# ... 并且立马使用它
print whisper()
# 你每次调用'talk',定义在talk里面的whisper同样也会被调用
talk()
# 输出 :
# yes...
# 但是"whisper" 不会单独存在:
try :
print whisper()
except NameError, e :
print e
#输出 : "name 'whisper' is not defined"*
函数引用
从以上两个例子我们可以得出,函数既然作为一个对象,因此:
1. 其可以被赋给其他变量
2. 其可以被定义在另外一个函数内
这也就是说,函数可以返回一个函数,看下面的例子:
def getTalk(type="shout") :
# 我们定义另外一个函数
def shout(word="yes") :
return word.capitalize()+" !"
def whisper(word="yes") :
return word.lower()+"...";
# 然后我们返回其中一个
if type == "shout" :
# 我们没有使用(),因为我们不是在调用该函数
# 我们是在返回该函数
return shout
else :
return whisper
# 然后怎么使用呢 ?
# 把该函数赋予某个变量
talk = getTalk()
# 这里你可以看到talk其实是一个函数对象:
print talk
#输出 : <function shout at 0xb7ea817c>
# 该对象由函数返回的其中一个对象:
print talk()
# 或者你可以直接如下调用 :
print getTalk("whisper")()
#输出 : yes...
还有,既然可以返回一个函数,我们可以把它作为参数传递给函数:
def doSomethingBefore(func) :
print "I do something before then I call the function you gave me"
print func()
doSomethingBefore(scream)
#输出 :
#I do something before then I call the function you gave me
#Yes !
这里你已经足够能理解装饰器了,其他它可被视为封装器。也就是说,它能够让你在装饰前后执行代码而无须改变函数本身内容。
手工装饰
那么如何进行手动装饰呢?
# 装饰器是一个函数,而其参数为另外一个函数
def my_shiny_new_decorator(a_function_to_decorate) :
# 在内部定义了另外一个函数:一个封装器。
# 这个函数将原始函数进行封装,所以你可以在它之前或者之后执行一些代码
def the_wrapper_around_the_original_function() :
# 放一些你希望在真正函数执行前的一些代码
print "Before the function runs"
# 执行原始函数
a_function_to_decorate()
# 放一些你希望在原始函数执行后的一些代码
print "After the function runs"
#在此刻,"a_function_to_decrorate"还没有被执行,我们返回了创建的封装函数
#封装器包含了函数以及其前后执行的代码,其已经准备完毕
return the_wrapper_around_the_original_function
# 现在想象下,你创建了一个你永远也不远再次接触的函数
def a_stand_alone_function() :
print "I am a stand alone function, don't you dare modify me"
a_stand_alone_function()
#输出: I am a stand alone function, don't you dare modify me
# 好了,你可以封装它实现行为的扩展。可以简单的把它丢给装饰器
# 装饰器将动态地把它和你要的代码封装起来,并且返回一个新的可用的函数。
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#输出 :
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
现在你也许要求当每次调用a_stand_alone_function时,实际调用却是a_stand_alone_function_decorated。实现也很简单,可以用my_shiny_new_decorator来给a_stand_alone_function重新赋值。
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#输出 :
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
# And guess what, that's EXACTLY what decorators do !
装饰器揭秘
前面的例子,我们可以使用装饰器的语法:
@my_shiny_new_decorator
def another_stand_alone_function() :
print "Leave me alone"
another_stand_alone_function()
#输出 :
#Before the function runs
#Leave me alone
#After the function runs
当然你也可以累积装饰:
def bread(func) :
def wrapper() :
print "</''''''\>"
func()
print "<\______/>"
return wrapper
def ingredients(func) :
def wrapper() :
print "#tomatoes#"
func()
print "~salad~"
return wrapper
def sandwich(food="--ham--") :
print food
sandwich()
#输出 : --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs :
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>
使用python装饰器语法:
@bread
@ingredients
def sandwich(food="--ham--") :
print food
sandwich()
#输出 :
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>