python的装饰器
看文章你必须了解:python函数的知识
python基础操作
刚学python的时候,对装饰器这个概念很是难理解。在百度和知乎上看过很多大佬的文章,但总没有醍醐灌顶的那种感觉。总是觉得好像懂了些什么,其实又不懂。
所以我决定写篇文章,用我的思路来来帮助看到这篇文章的读者理解python中的装饰器,希望能让大家少踩些坑。
名词解释
我觉得要了解装饰器,首先要明白什么是装饰器,装饰器拿来干嘛用,装饰器为什么这么写。这就是看完这篇文章你能知道的全部装饰器:
从字面上的意思来说,装饰器。起到的是装饰的作用,其实说白了就是给函数加一个新的功能。
他一般长成下面这个样子:
def decorator(f):
def wrapper():
f()
return wrapper
@decorator
function():
some code
return ....
其中decorator就是装饰器了。可能看到这边会有点懵,到后边会慢慢讲闭包:
在我们讲装饰器的时候,要先讲一下闭包。闭包是一种奇特的函数
变量的作用域
wr = 1
def foo():
wr = wr+1
return wr
我创建了一个变量wr,和一个函数foo()。大家看一下在foo函数中是不是能取到函数外的wr变量呢?当然是可以的
然后看下一个例子
def foo():
wr =1
def wrapper():
wr = wr+1
return wr
那么问题来了,在这个foo函数中我嵌套了一个wrapper函数,在wrapper函数中能不能取到在foo函数中的wr变量呢,其实也是可以的
wrapper函数使用到wr这个变量的时候,他先在自己函数内部寻找wr变量,发现没找到之后就会在他的上级,也就是这里的foo函数中寻找,然后他找到了wr = 1。如果foo中也没有找到wr变量的话,还会继续向更上一级寻找。
python中的一切都是对象
然后我们这边要讲一句python中很经典的一句话,就是python中的一切都是对象,其实我们实现的每个函数,都是顶级父类object的子类。这句话看不懂没关系,我们来举例子:
def foo():
print("im foo function!")
fun = foo
大家觉得这段代码对吗,看起来我把foo赋值给了fun,那么fun里面到底是什么呢,我们来print一下吧:
print(fun)
在我加上一个print来把fun输来看到下面的提示。没有报错说明我们的程序没有错,那么输出的是什么呢?
function foo at 一串奇怪的东西,这串奇怪的东西其实是一串16进制的值,是函数foo在内存中的地址。原来我们在代码中打fun = foo
的时候其实是把foo函数的内存地址给了fun啊
那我们想想我们之前是怎么调用foo函数的?
def foo():
print("im foo function!")
foo()
是不是这样,用 foo()来调用。那么我们是不是就可以得出:
def foo():
print("im foo function!")
fun = foo
fun()
这样我们就给foo函数改了个名字变成了fun,大家可以把代码放到pycharm里试试,把fun()和foo()的答案是不是一样的
知道这两样之后,我们可以继续来了解闭包了:
我们先不谈闭包到底是干嘛的,到后面会有演示,我们刚才说过,闭包是一种奇特的函数,那么我们就先搞清楚什么样的函数才能称得上叫做闭包这个名字:闭包函数必须有内嵌函数
内嵌函数需要引用该嵌套函数上一级namespace中的变量
闭包函数必须返回内嵌函数
嗯,达成这三样我们就能创建一个闭包了,那大家和我一起来创建一个吧
闭包函数必须有内嵌函数:
先达成第一件,我们要先有个内嵌函数:
直接抄上面的例子
def foo():
def wrapper():
print("i want have a closure!")
好了好了,现在我有个内嵌函数了,在foo中嵌了个wrapper函数。
内嵌函数需要引用该嵌套函数上一级namespace中的变量
然后第二步其实就是说嵌在里面的那个函数要引用外面那个函数里面的一个变量才行。那这个简单啊,我可以
def foo():
a = 1
def wrapper():
a = a+1
print("i want have a closure!")
嵌套在foo函数中的wrapper函数引用了foo函数中的a变量
嘿嘿,先别管这个函数有什么卵用,我们先来实现一个闭包先,然后我们这样也可以完成第二点:
def foo(a):
def wrapper():
print(a)
然后我们来达成第三步
闭包函数必须返回内嵌函数
这个就更简单了嘛,加个return嘛,但是这里要注意了,是闭包函数返回内嵌函数,也就是说,套在外边的那个函数用return返回套在里面的那个函数的内存地址
也就是foo函数return wrapper函数的内存地址
def foo(a):
def wrapper():
print(a)
return wrapper
这样我们就亲手创建了一个闭包,下面我们就要讲讲什么时候我们会用到闭包了:
用途解释
首先我们来假象一个需求,我们要创建两个个函数foo、bar foo函数只要做一件事情,print一个hello
bar函数只要做一件事情,print一个world,这个简单啊对不对,大家都会:
def foo():
print("hello")
def bar():
print("world")
好了,这个函数做得不错,大家都采用了这个方案并在很多代码中调用了这个函数。
有一天产品经理过来说要给所有函数加一个功能,打印这个函数的运行时间(mmp,就输出个hello world还要我打印运行时间)。产品经理的小小要求还是要满足的:
import time
def foo():
startTime = time.time()
print("hello")
stopTime = time.time()
print(stopTime - startTime)
def bar():
startTime = time.time()
print("world")
stopTime = time.time()
print(stopTime - startTime)
但是这样虽然成功了,万一有很多函数到时候都要打印运行时间,那岂不是要完蛋。并且这种代码被同行看见会被笑话的,不行不行不行。改一改:
import time
def timeFUN(fun):
startTime = time.time()
fun()
stopTime = time.time()
print(stopTime - startTime)
def foo():
print("hello")
def bar():
print("world")
timeFUN(foo)
写完之后瞬间觉得自己的智商碾压大众,让他们以后要用某个函数想知道运行时间的时候就用timeFUN()函数。
但是!那foo函数和bar函数可能已经上线了,被做成接口已经在很多地方应用了,改了调用方式相当于说要和所有用了foo和bar这两个函数的人都说一次:“接口改了,把那一万行里面的foo函数全部改成timeFUN(foo)”。这当然不行啊,怕是要被其他人打死。得想想其他办法。这个时候我们想到了闭包的移花接木大法,具体操作:
import time
def timeFUN(fun):
def wrapper()
startTime = time.time()
fun()
stopTime = time.time()
print(stopTime - startTime)
return wrapper
def foo():
print("hello")
def bar():
print("world")
foo = timeFUN(foo)
bar = timeFUN(bar)
foo()
很显然timeFUN是一个标准的闭包吧,timeFun返回了wrapper函数的返回地址,这个时候我们把timeFun返回的wrapper函数的返回地址再赋值回foo,是不是有点绕,多看几遍这句话自己敲下代码
经过这一顿骚操作,瞬间完成了需求。
最后,其实python给了我们一个更方便的方法可以偷懒不用写foo = timeFUN(foo)
这种赋值了
最后完美版代码:
import time
def timeFUN(fun):
def wrapper()
startTime = time.time()
fun()
stopTime = time.time()
print(stopTime - startTime)
return wrapper
@timeFUN
def foo():
print("hello")
@timeFUN
def bar():
print("world")
foo()
bar()
是不是和开头的例子很像呢,这就是python的装饰器的基础运用。其实装饰器就是闭包的一种啦。下篇有时间我来讲讲装饰器的高级运用,如果foo这个函数有参数又该怎么办呢?
最后:
文章本来名字叫做python的装饰器(上),写了有三个半月了,拿到了七个赞,心酸酸。换个标题当个标题党看看会不会点赞激增呢?
因为本人的水平有限可能会有许多地方理解和叙述有出入,希望大家能提出来防止我误人子弟。分享是好事,但是如果因为我理解上有问题,将来报道上有了偏差,我可是要负责任的