一篇文章就能彻底明白python装饰器
引子
假设很久之前你写过一个函数,现在你突然有了个想法就是你想看看,以前那个函数在你数据集上的运行时间是多少,这时候你可以修改之前代码为它加上计时的功能,但是这样的话是不是还要大体读读你之前的这个的代码,稍微搞清楚一点它的逻辑,才敢给它添加新的东西。这样是不是很繁琐,要是你之前写的代码足够乱足够长,再去读它是不是很抓狂…。实际工作中,我们常常会遇到这样的场景,可能你的需求还不只是这么简单。那么有没有一种可以不对源码做任何修改,并且可以很好的实现你所有需求的手段呢?
答案当然是有,这就是要介绍的python装饰器。
所以说:装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象(函数的指针)
要想理清楚装饰器,我们必须先了解下面几样东西哦,如果对这些全部了解的话,那么装饰器就很简单了。
函数
函数是什么呢?
在python中,使用关键字def和一个函数名以及一个可选的参数列表来定义函数,函数使用return关键字来返回值,我们一般说的函数是指函数名,比如foo,而foo()已经执行函数了,foo()是什么类型取决于return的内容是什么类型!!!
函数即对象
在python的世界里,函数和[1,2,3],‘abc’,8等一样都是对象,而且函数是*的对象(对象是类的实例化,可以调用相应的方法,函数是包含变量对象的对象)。
函数对象的调用仅仅比其他对象多了一个()而已,foo,bar和a,b一样都是个变量名,为什么函数只有加载到内存里面才可以被调用?
注意:这里说的函数都是指函数名,比如foo;而foo()已经执行函数了,foo()是什么类型取决于return的内容是什么类型!!!
既然函数是对象,那么自然满足下面两个条件:
- 可以被赋值于其他变量
- 可以被定义在另外一个函数内(作为参数&作为返回值),类似于整形,字符串等对象
函数的嵌套以及闭包
先抛一个小问题:bar()是什么?
def foo():
print('foo')
def bar():
print('bar')
# bar()
bar()
是的,bar就是一个变量名,有自己的作用域的。
Python允许创建嵌套函数。通过在函数内部def的关键字再声明一个函数即为嵌套:
闭包:如果在一个内部函数里,对在外部作用域(但不是全局作用域)的变量进行引用,那么内部函数就认为是闭包。
闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域
举些例子
示例1:
def ounter():
name = 'duart'
def inner(): #闭包条件一 inner就是内部函数
print("在inner里打印外层函数的变量",name)
#闭包条件二,外部环境的一个变量
return inner
#结论,内部函数inner就是一个闭包
f = ounter()
print(f())
# 结果:
# 在inner里打印外层函数的变量 duart
# None
示例2:
def make_adder(addend):
def adder(augend):
return augend+addend
return adder
res1 = make_adder(12)
res2 =make_adder(13)
print(res1(100))
print(res2(100))
# 运行结果:
# 112
# 113
分析一下: 我们发现,make_adder是一个函数,包括一个参数addend,比较特殊的地方是这个函数里面又定义了一个新函数,这个新函数里面的一个变量正好是外部make_adder的参数.也就是说,外部传递过来的addend参数已经和adder函数绑定到一起了,形成了一个新函数,我们可以把addend看做新函数的一个配置信息,配置信息不同,函数的功能就不一样了,也就是能得到定制之后的函数. 再看看运行结果,我们发现,虽然p和q都是make_adder生成的,但是因为配置参数不同,后面再执行相同参数的函数后得到了不同的结果.这就是闭包.
示例3:
def hellocounter(name):
count = [0]
def counter():
count[0] += 1
print("hello ",name,',',str(count[0])+'access!')
return counter
hello = hellocounter('make_program')
hello()
hello()
hello()
print(hello())
# 结果:
# hello make_program , 1access!
# hello make_program , 2access!
# hello make_program , 3access!
# hello make_program , 4access!
# None
程序分析: 这个程序比较有趣,我们可以把这个程序看做统计一个函数调用次数的函数.count[0]可以看做一个计数器,每执行一次hello函数,count[0]的值就加1。也许你会有疑问:为什么不直接写count而用一个列表?这是python2的一个bug,如果不用列表的话,会报这样一个错误:UnboundLocalError: local variable ‘count’ referenced before assignment. 什么意思?就是说conut这个变量你没有定义就直接引用了,我不知道这是个什么东西,程序就崩溃了.于是,再python3里面,引入了一个关键字:nonlocal,这个关键字是干什么的?就是告诉python程序,我的这个count变量是再外部定义的,你去外面找吧.然后python就去外层函数找,然后就找到了count=0这个定义和赋值,程序就能正常执行了
示例4:
def makebold(n):
def wrapped():
return n()
return wrapped
def makeitalic(n):
def wrapped():
return n()
return wrapped
@makebold
@makeitalic
def hello():
return 'hello word'
print(hello())
# hello word
怎么样?这个程序熟悉吗?这不是传说的的装饰器吗?对,这就是装饰器,其实,装饰器就是一种闭包,我们再回想一下装饰器的概念:对函数(参数,返回值等)进行加工处理,生成一个功能增强版的一个函数。再看看闭包的概念,这个增强版的函数不就是我们配置之后的函数吗?区别在于,装饰器的参数是一个函数或类,专门对类或函数进行加工处理。python里面的好多高级功能,比如装饰器,生成器,列表推到,闭包,匿名函数等,开发中用一下,可能会达到事半功倍的效果!
闭包的用途
装饰器
什么是装饰器
装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数。本质上就是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象,它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
举例
业务生产中大量调用的函数:
def foo():
print('hello foo')
foo()
现在有一个新的需求,希望可以记录下函数的执行时间,于是在代码中添加日志代码:
import time
def foo():
start_time=time.time()
print('hello foo')
time.sleep(3)
end_time=time.time()
print('spend %s'%(end_time-start_time))
foo()
bar()、bar2()也有类似的需求,怎么做?再在bar函数里调用时间函数?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门设定时间:
import time
def show_time(func):
start_time=time.time()
func()
end_time=time.time()
print('spend %s'%(end_time-start_time))
def foo():
print('hello foo')
time.sleep(3)
show_time(foo)
逻辑上不难理解,而且运行正常。 但是这样的话,你基础平台的函数修改了名字,容易被业务线的人投诉的,因为我们每次都要将一个函数作为参数传递给show_time函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行foo(),但是现在不得不改成show_time(foo)。那么有没有更好的方式的呢?当然有,答案就是装饰器。
if foo()==show_time(foo) :问题解决!
所以,我们需要show_time(foo)返回一个函数对象,而这个函数对象内则是核心业务函数:执行func()与装饰函数时间计算,修改如下:
import time
def show_time(func):
def wrapper():
start_time=time.time()
func()
end_time=time.time()
print('spend %s'%(end_time-start_time))
return wrapper
def foo():
print('hello foo')
time.sleep(3)
foo=show_time(foo)
foo()
函数show_time就是装饰器,它把真正的业务方法func包裹在函数里面,看起来像foo被上下时间函数装饰了。在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。
最终版
@符号是装饰器的语法,在定义函数的时候使用,避免再一次赋值操作
如上所示,这样我们就可以省去bar = show_time(bar)这一句了,直接调用bar()即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。
这里需要注意的问题: foo=show_time(foo)其实是把wrapper引用的对象引用给了foo,而wrapper里的变量func之所以可以用,就是因为wrapper是一个闭包函数。
上一篇: 实验:DHCP中继
下一篇: 机器人警察即将上岗,警察要下岗