网上面有很多优秀的文章写得很好,但是每个人的思路和接受的方式都不一样,我选用了自己能看得懂再加上自己的理解写了这篇博客,将分为多步实现对装饰器的理解,作为新手入门级别,另外会在结束后,给上我认为优秀文章的链接。。。。帮助有缘人彻底起飞
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
原则:1.不能修改被装饰函数的源代码
2.不能修改被装饰函数的调用方法
废话不多说,开整吧!
第一步:准备给一个函数添加一个新的功能
def foo(): time.sleep(3) print('in the foo') foo()
我首先想到会这样做
def foo(): start_time = time.time() time.sleep(3) print('in the foo') end_time = time.time() print('code run time is %s'%(end_time-start_time)) foo()
直接修改源码,违背原则1,显然任务量很大,而且很low
于是乎,我想到了似low非low
def timer(): start_time = time.time() foo() end_time = time.time() print('code run time is %s' % (end_time - start_time)) timer()
或者
def timer(func): start_time = time.time() func() end_time = time.time() print('code run time is %s' % (end_time - start_time)) timer(foo) 直接写一个计算运行时间的函数,把需要添加功能的函数放进来,这样就可以不改变源码了,但是调用方法却从foo()---->timer()和timer(foo),违背原则2,假设foo()在多处被修改,
那么我们是不是也要去把所有的foo()换成timer(foo)呢?
但是不要着急,因为我们已经实现了不改变源码那一步,现在只需要将调用方法timer(foo)变成-----》foo()即可,我们何不把timer赋值给foo,等等,timer似乎带有一个参数.......
显然你不可能把timer(foo)赋值给foo,然后再调用foo(),这个为什么不能的原因如果不知道就扇自己一个大嘴巴子吧........什么?扇完还不知道!timer(foo)是函数调用的结果,结果
能当函数调用吗.....我们要想timer(foo)不是直接产生调用结果,那就必须让他返回一个与foo参数一致的函数,然后就可以把timer(foo)赋值给foo,再调用foo()
怎么才能做到这一点呢?必须要用到嵌套函数,在timer函数体里再def定义一个函数,然后返回这个函数
def timer(func): def deco(): start_time = time.time() func() end_time = time.time() print('code run time is %s' % (end_time - start_time)) return deco foo=timer(foo) foo()
可以说,基本上已经完成了一个装饰器,粘贴一段话以示尊敬
在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。
你以为就这样结束了???so young
现在foo()里有参数了
def timer(func): def deco(a,b): start_time = time.time() ret = func(a,b) end_time = time.time() print('code run time is %s' % (end_time - start_time)) return ret return deco @timer def foo(a,b): time.sleep(3) print('in the foo') return a+b foo(1,2)
或许大家注意到了@timer,这个叫做语法糖,有了这个就可以省去foo= timer(func)
现在不确定往foo()传什么参数,还好python里面有*args和**kwargs,可变参数
def timer(func): def deco(*args,**kwargs): start_time = time.time() ret = func(*args,**kwargs) end_time = time.time() print('ret is %s,code run time is %s' % (ret,end_time - start_time)) return ret return deco @timer def foo(a,b): time.sleep(3) print('in the foo') return a + b @timer def foo2(a,b,c): time.sleep(3) print('in the foo') return a + b +c foo(1,2) foo2(1,2,3)
还可以让装饰器带参数
import time def timeit(*targs,**tkwargs): def timer(func): def deco(*args, **kwargs): start_time = time.time() ret = func(*args, **kwargs) end_time = time.time() print('task is %s, %s,ret is %s,code run time is %s' % (targs,tkwargs,ret,end_time - start_time)) return ret return deco return timer @timeit(1,task='timeit') def foo(a, b): time.sleep(3) print('in the foo') return a + b @timeit(1231) def foo2(a, b, c): time.sleep(3) print('in the foo') return a + b + c print('-------') foo(1, 2) foo2(1, 2, 3)
装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数。
函数是python中的‘一等公民’,函数可以作为参数传递给另一个函数
Python 的作用域规则是, 变量的创建总是会创建一个新的局部变量,但是变量的访问(包括修改)在局部作用域查找然后是整个外层作用域来寻找匹配。当函数体中遇到未认证的变量时,python会依次搜索4个作用域(本地作用域,上一层def本地作用域,全局,最后是内置)并且在第一次找到这个变量的地方停下来,如果这个变量名始终没有找到就会报错。
x=2
def foo():
x=1
print(x)#1
foo()
print(x)#2
变量的生命周期,下面这个函数的错误不仅仅是NameError导致,也因为foo()的作用域因foo()调用时重新创建,因foo()结束时而销毁,变量x在foo()结束时已经不存在
def foo():
x=1
foo()
print(x)
NameError: name 'x' is not defined
闭包的定义:如果在一个内部函数里,对在外部函数内(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)
def outer():
x=1
def inner():
print(x)#1
return inner
foo=outer()
foo()
Python 中一切都按作用域规则运行—— x
是函数 outer
中的一个局部变量,当函数 inner
在 #1
处打印 x
时,Python 在 inner
中搜索局部变量但是没有找到,然后在外层作用域即函数 outer
中搜索找到了变量 x
。
但如果从变量的生命周期角度来看应该如何呢?变量 x
对函数 outer
来说是局部变量,即只有当 outer
运行时它才存在。只有当 outer
返回后才能调用 inner
,所以依据 Python 运行机制,在调用 inner
时 x
就应该不存在了,那么这里应该有某种运行错误出现。
结果并不是如此,返回的 inner
函数正常运行。Python 支持一种名为函数闭包的特性,意味着在非全局作用域定义的 inner
函数在定义时记得外层命名空间是怎样的。inner
函数包含了外层作用域变量,通过查看它的 func_closure
属性可以看出这种函数闭包特性。
记住——每次调用函数 outer
时,函数 inner
都会被重新定义。此时 x
的值没有变化,所以返回的每个 inner
函数和其它的 inner
函数运行结果相同
def outer(x):
def inner():
print(x)
return inner
foo=outer(1)
foo2=outer(3)
foo()#1
foo2()#3
从这个示例可以看到闭包——函数记住其外层作用域的事实——可以用来构建本质上有一个硬编码参数的自定义函数。虽然没有直接给 inner
函数传参 1 或 2,但构建了能“记住”该打印什么数的 inner
函数自定义版本。