Python攻克之路-装饰器
装饰器(函数)
准备条件
a.作用域:LEGB
b.高阶函数:a.函数名可以作为参数输入 b.函数名字可以作为返回值
c.闭包
1.闭包函数
定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就认为是闭包(closure)
例子分析: In [1]: def f(): ...: c=5 ...: f() ...: print(c) #当f()执行时,可以调用c,f函数执行结束后无法再调用c NameError: name 'c' is not defined [root@test day16]# cat test.py #!/usr/local/python3/bin/python3 # def outer(): x=10 def inner(): ##条件1:是一个内部函数 print(x) ##条件2:相对于inner,x是outer的,也就是外部环境的引用 return inner ##结论:内部函数inner就是一个闭包,满足后,即使x=10在outer函数已经执行完,inner还是可以引用 #call inner outer()() #调用outer #or another way #f=outer() #f() #这种调用是在outer函数的外部,执行时调用了x,执行是在inner里print(x)的代码,执行时调用的是outer 的x的变量,但是当执行f()时,实际f=outer()是已经执行完了,也就是x=10也已经执行完成了,执行f()时 是执行inner函数里面的代码,按常理是不能再调用已经执行完成outer函数里面的x=10的代码,这里还能 够调用,这种现象就是闭包 #inner() #不能直接调用inner,因为inner里的是局部变量,在全局无法调用
Summary: 闭包=函数块(inner函数)+定义函数时的环境(x=10,脱离outer还可以引用)
2.装饰器函数分析
场景分析:写了一个函数,需要查看它的执行时间
时间统计
In [1]: import time In [2]: start=time.time() In [3]: time.sleep(2) In [4]: end=time.time() In [5]: end-start Out[5]: 21.97436809539795 单位是秒 In [6]: start Out[6]: 1521506448.4554262 ##unix的时间,从unix诞生到现
实现方法一
在已经写好的基本上修改,存在一个开放封闭的问题,如一个大型的电商站点是由各个模块组成的,特别是登陆这种基本上任何一个模块都要调用它的基础模块,如果直接对它做修改,一旦发生了错误所有调用它的模块就可能会出现问题,所以就产生一种需求,一旦这个实现了,不管性能好坏,不再允许其他人对它作出修改的封闭性原则,开放性原则是不允许对它原代码做修改,但是可以对它进行扩展,所以对原代码修改封闭,对扩展进行开放
In [7]: def f(): ...: start = time.time() ...: print('you are great..') ...: time.sleep(1) ...: end = time.time() ...: print('your function time is %s'%(end-start)) In [8]: f() you are great.. your function time is 1.0002365112304688
实现方法二
描述: 写一个固定的计时函数,其他来调用它,这里存在一个问题,foo,bar是作为基础函数给另的函数调用的,但是在这里修改了调用的方法,原来是foo(),bar()这样调用的,现在使用show_time(foo),show_time(bar)来调用,会影响所有的业务都要修改这种调用方式
In [5]: import time In [6]: def foo(): ...: print('foo') In [7]: def bar(): ...: print('bar') In [8]: def show_time(f): #使用一个动态来变量来实现,函数的名字,计算时间的功能函数,它调用什么就,传入什么 ...: start = time.time() ...: f() #传入什么功能函数,就是什么功能函数 ...: end = time.time() ...: print('spend %s' %(end - start)) In [9]: show_time(foo) #直接把函数名传入作参数调入 foo spend 2.6226043701171875e-05 In [10]: show_time(bar) bar spend 2.6464462280273438e-05
实现方法三:(最终实现)
描述:装饰器是一个特殊的函数,装饰就是为之前的函数添加一个新的功能,在如下代码中,foo,bar是原来的功能函数,show_time是新添加的一个计算时间的功能函数,里面有一个inner函数,这时这个show_time就是一个装饰器函数,
a. 最终要实现需要是把foo,bar的功能实现,还要对它会作计时,而且最后调用时,还是使用foo(),bar(),不修改原来的调用方式
b. 思路1:进行变量赋值,重新定义一个foo=show_time(),然后直接调用foo(),这时show_time和foo是同指一个内存地址,而且这里的show_time本身定义时就有一个参数f,现在foo()调用时是没有的,存在问题
c. 思路2:foo=show_time(foo),foo(),这样是函数执行,不是变量赋值,show_time(foo)是直接执行,执行是show_time(f)这个函数,执行完后return一个none(show_time(foo),最后相当于none()
d. 思路3:最终是要将show_time(foo)与foo()的调用效果一样,foo()最后执行是原函数foo和计算时间这两件,实际上在foo()上要实现的两件事,在show_time(f)中已经实现了,核心处理的四行代码,修改为在show_time函数里嵌套一个inner函数,再返回一个inner,再执行show_time(foo)时,就直接把整个inner函数存放在内存中,并不会执行,直接得到是定义inner函数的一个内存地址,也就是返回的inner,所以这里show_time(foo)执行完后是得到一个内存地址,可以把它赋值给一个变量,这样就可以foo=show_time(foo),通过foo()调用,foo是show_time(foo)这个函数的一个返回结果,它执行完后,通过return inner是直接返回一个函数的内存地址,再执行foo()就相当于执行inner函数,是一个闭包函数
e. 思路3.1:针对思路3中多次调用都要增加foo=show_time(foo)的写法修改,把foo=show_time(foo)替换成@show_time,它是直接找下面的函数foo(),相当于show_time(foo),将来如果那个功能需要调用时就直接在某个函数上使用@show_time就可以了.
In [1]: import time In [2]: def foo(): ...: print('foo') ...: time.sleep(1) In [3]: def bar(): ...: print('bar') ...: time.sleep(2) In [4]: def show_time(f): ...: def inner(): ...: start = time.time() ...: f() ...: end = time.time() ...: print('spend %s' % (end - start)) ...: return inner In [5]: foo=show_time(foo) In [6]: foo() ##相当于inner函数,执行时间,执行了f(),当foo=show_time(foo),这时f是foo,所以inner是闭包函数,f是它的外部环境 foo spend 1.0010957717895508 In [7]: bar=show_time(bar) In [8]: bar() bar spend 2.001352071762085 #优化:把foo=show_time(foo)替换成@show_time In [9]: import time In [10]: def show_time(f): ...: def inner(): ...: start = time.time() ...: f() ...: end = time.time() ...: print('spend %s' % (end - start)) ...: return inner In [11]: @show_time #相当于foo=show_time(foo) ...: def foo(): ...: print('foo') ...: time.sleep(1) In [12]: @show_time ...: def bar(): ...: print('bar') ...: time.sleep(2) In [13]: foo() foo spend 1.001082420349121 In [14]: bar() bar spend 2.0026755332946777
3.装饰器之被装饰函数(功能函数)的参数
需求:功能函数foo是没有参数的,要实现foo做成一个两位的加法器
定长
In [1]: import time In [2]: def show_time(f): ...: def inner(x,y): ...: start = time.time() ...: f(x,y) ...: end = time.time() ...: print('you time is %s' % (end - start)) ...: return inner In [4]: @show_time ...: def add(a,b): ...: print(a+b) ...: time.sleep(1) In [5]: add(1,2) ##当传入1,2时会找到show_time函数中inner函数的x,y,执行到f(x,y)相当于add(a,b) 3 you time is 1.0088708400726318
不定长
In [13]: def show_time(f): ...: def inner(*x,**y): ##无名参数,有名参数 ...: start = time.time() ...: f(*x,**y) ## ...: end = time.time() ...: print('you time is %s' % (end - start)) ...: return inner In [14]: @show_time ...: def add(*a,**b): ...: sums=0 ...: for i in a: ...: sums+=i ...: print(sums) ...: time.sleep(1) In [15]: add(1,2,3,4) #加法器一般传无名参数就可以,传入的1,2,3都到a,a是一个元组 1 3 6 10 you time is 4.004520654678345
4.装饰器之装饰函数的参数
需求:添加一个日志的功能,计算函数执行时间时把它记录到日志,按照之前的写法,在装饰器函数中直接添加一个日志功能就可以,使用@show_time就会直接调用,但是这里需要时功能函数foo,bar等要传入一个参数如true or false来决定是否触发这个功能的使用写入到日志中分析:实现中需要一个参数来决定是否需要写到日志
思路1:传入参数show_time(f,flag),@show_time(add)时就是add=show_time(add)的调用,已经是固定了,再加一个flag是无法实现
思路2:再嵌套一层函数,def logger(flag),@logger('true')就可以使用参数(参数随意,取决于后期判断),会先执行logger函数,而且传入一个参数,这时logger函数返回是show_time,这时@logger('true')实际返回就是@show_time,相当于logger(flag)只是使用一个变量,那么@show_time也可以使用变量了,logger(flag)内容取决于@logger('true')里传入的内容,实际是一个闭包,就相当于做了一个外层函数提供了一个变量,有了flag这个参数,就可以在inner函数作判断if,为了方便后面调用logger(flag='')写成默认参数
In [10]: import time In [11]: def logger(flag): ...: def show_time(f): ...: def inner(*x,**y): ...: start=time.time() ...: f(*x,**y) ...: end=time.time() ...: print('you time is %s'%(end-start)) ...: if flag=='true': ...: print('log') ...: return inner ...: return show_time In [12]: @logger('true') ...: def add(*a,**b): ...: sums=0 ...: for i in a: ...: sums+=i ...: print(sums) In [13]: add(2,3) 2 5 you time is 3.8623809814453125e-05 log