在讲到python内置函数中的map等函数时讲到过函数式编程,map函数将另一个函数名作为参数使用,其实在python中的,我们在声明函数时,函数名称会当作储存函数的符号指向了函数的内存地址,所以函数名还可以做为一个普通的数据传递给任意变量甚至储存到列表中,如:
def sun(): ..... f = sun
这时f() 和sun()完全等价
所以可以认为一个单纯的函数名称只代表一个指向一个地址的参数,当这个参数后面加上“()”时,这个函数才会被执行。
我们在日常的开发过程中可以知道,一个函数包装后的代码块,一般都是一个完整的功能,如果现在出现一个情况,需要增加你某个函数的部分功能,又不希望修改调用方式,如果我们直接去修改函数,可能会承担函数被修改坏的风险,那有没有什么方法可以不对函数做直接修改,又不改变调用方式的情况下为一个函数增加部分功能呢?答案就是使用装饰器。
为了方便理解,先不直接讲解装饰器,我们假设有一个看视频的网站,只要是打开了网页就可以直接观看视频,假设其中的逻辑如下:
def tv(): print("假装在看视频") tv()
后来网站有名了,膨胀了,需要有会员才能观看视频,现在又要给加一个验证登录的功能,我们假装tv()方法非常复杂,如果直接修改,可能一不小心导致程序崩溃,所以想要在函数外面来增加这个功能,但是在其他程序中调用tv()函数时,调用方式不变,该如何处理呢?不慌,我们先写一个实现登录功能的函数
def login(): print("假装在验证")
那么如何在调用tv()时,能够运行login方法呢?
答案是我们可以将tv作为一个变量,然后将login传递给tv,这样调用tv()时,执行的就是login函数,然后再将真正的tv()函数在login中执行:
def tv(): print("假装在看视频") def login(): print("假装在验证") tv() tv = login tv()
但是这个时候又有问题了,因为在执行login时,tv已经被替换成了login函数,所以在login函数内的tv()实际上还是执行的login(),这时候我们可以使用第三个变量来储存真正的tv函数,就可以解决这个问题:
def tv(): print("假装在看视频") def login(): print("假装在验证") tv2() tv2 = tv tv = login tv()
-----------------------------
假装在验证
假装在看视频
可以看到返回的结果正是我们想要达到的效果,非常的完美对不对?不对!在login函数中我们写死了函数名,那么我们的login函数,就只能使用一次,作为一个优秀的程序员,怎么能写出复用率这么低的代码呢?函数是可以传递参数的,我们完全可以将函数名作为参数传递给login函数,怎么做呢?如果只是稍作修改,代码就是如下:
def tv(): print("假装在看视频") def login(func): print("假装在验证") return func tv = login(tv) tv()
我们将login修改成了需要一个参数的函数,然后我们将tv函数作为参数传递过去,然后再执行完login函数后,将tv函数作为返回值传递回tv,之前的tv=login 是将login的内存地址传递给tv,而tv = login(tv)是表示执行login()函数后,将返回值传递给tv,所以必须要有返回值,并且tv要能被调用,所以只能返回一个函数给tv,如是就成了上面的代码,运行后结果也是我们想要的效果,是不是很完美?仔细想想login是在何时被执行的,并非是在调用tv时执行,而是在执行 tv = login(tv) 这一段时就被执行了。那么现在问题来了,我们想要在调用tv时才执行login,为tv赋值时不会执行任何逻辑,我们要怎么做呢?没错,这个地方才是真正开始将装饰器,上面的都是废话!
想想看,我们想要在执行tv = login(tv)时,只是将一个函数传递给tv,而不会执行任何逻辑,login函数该怎么写?首先,肯定是要有一个返回值,并且这个返回值肯定是一个函数,如是我们可以先这么写:
def login(func): return funcx
当然上面代码会是错的,因为funcx找不到,funcx应该是谁呢?login?不行,因为tv()函数没有参数,而login函数有参数。tv?不行,那就相当于我们什么都没有做,所以这个funcx只能是一个新函数,我们用这个函数代替之前的login函数,只有执行funcx时,才会执行验证的逻辑,并且执行tv函数。那么问题又来了,如果funcx是在调用tv()时调用,那么这个函数内又如何执行真正的tv()呢?总不能写死吧?那和一开始就没有区别了,索性python里函数有一种嵌套写法,可以在一个函数内再写一个函数,所以我们最终的样子就是:
def tv(): print("假装在看视频") def login(func): def funcx(): print("假装在验证") return func() return funcx tv = login(tv) tv()
这个时候就是真正的 解决了我们的问题,当tv()去掉时,整个程序,什么都不会发生,可以自行测试。
然后,python中的装饰器就是为了简化这种情况下的代码:
def login(func): def funcx(): print("假装在验证") return func() return funcx @login def tv(): print("假装在看视频") tv()
这种写法和上面的效果是一样的。
然后然后,再假设,如果我们的tv函数需要参数,或者有返回值怎么办呢?下面的写法完美的解决了这两种情况
def login(func): def funcx(name): print("假装在验证") return func(name) return funcx @login def tv(name): print(name,"假装在看视频") return 1 a = tv("yq") print("a=",a)
理解起来应该不难,就不多做解释了,有时候可能会要同时满足几个函数增加同一个功能的要求,而其他函数需要的参数又各不相同,这个时候可以利用可变参数将我们的装饰器进行升级。
def login(func): def funcx(*args,**kwargs): print("假装在验证") return func(*args,**kwargs) return funcx
这样就随你有没有参数,什么样的参数,有没有返回值了。
这就完了?并没有,装饰器自身还可以有参数的,因为用的不会不多,所以不做太多的假设,直接上代码。
def login(level): def inner(func): def funcx(*args,**kwargs): print("假装在验证") if level == "VVIP": print("欢迎土豪爸爸") return func(*args,**kwargs) return funcx return inner @login("VIP") def tv(name): print(name,"假装在看视频") @login("VVIP") def tv2(name): print(name,"假装在看视频") #tv("yq") tv2("yq")
这个里面在装饰器后面增加的参数,其实就只需要在login函数外面再增加一层,没错,你看的是像里面增加了一层,其实只是名字没有修改,你可以当作是在原有的基础上在外面增加了一层,而外面这层的逻辑很简单,就是将装饰器传来的参数接收一下,然后再调用原来的装饰器函数即可。
装饰器的内容就这么多,可以说是将函数式编程表现的淋漓尽致,其实重点就在于一个函数的内存地址随你怎么传,只要是加了“()”就变成了执行函数,在没有"()"之前,就是一个数据而已。弄懂了这个和装饰器的原理,那再复杂的装饰器也就只是简单的逻辑问题了。