不少人对装饰器难以理解,缘由是因为如下三点内容没有搞清楚:python
装饰器实际上就是为了给某程序增添功能,但该程序已经上线或已经被使用,那么就不能大批量的修改源代码,这样是不科学的也是不现实的,由于就产生了装饰器,使得其知足:数据库
那么根据需求,同时知足了这三点原则,这才是咱们的目的。由于,下面咱们从解决这三点原则入手来理解装饰器。闭包
等等,我要在需求以前先说装饰器的原则组成:app
这个式子是贯穿装饰器的灵魂所在!ide
装饰器:本质就是函数,功能是为其余函数添加功能; #原则 #1:不能修改被修饰函数的源代码; #2:不能修改被修饰函数的调用方式 #装饰器的知识储备 #装饰器 = 高阶函数+函数嵌套+闭包 import time def demo(func): def data(): star_time = time.time() func() end_time = time.time() print("执行时间%s"%(end_time - star_time))return data @demo #至关于inner=demo(inner) def inner(): time.sleep(1) print("执行结果") inner() # inner() #执行的是data # inner=demo(inner) #返回的是data的地址 # print(inner)# ,<function demo.<locals>.data at 0x00000128A495E950>
给功能函数加上返回值:函数
import time def demo(func): def data(): star_time = time.time() res = func() #就是在运行inner,用res接收inner的返回值 end_time = time.time() print("执行时间%s"%(end_time - star_time)) return res return data @demo def inner(): time.sleep(1) print("执行结果") return "这是inner的返回值" print(inner()) #执行inner并打印inner的返回值
给功能函数加上参数:spa
import time def demo(func): def data(*args,**kwargs): star_time = time.time() res = func(*args,**kwargs) #就是在运行inner,用res接收inner的返回值 end_time = time.time() print("执行时间%s"%(end_time - star_time)) return res return data @demo def inner(name,age): time.sleep(1) print("执行结果,名字是【%s】年龄是【%s】"%(name,age)) return "这是inner的返回值" print(inner("alex",18)) #执行inner并打印inner的返回值
给函数加上认证功能:code
user_list = [ {"name":"alex","passwd":"123"}, {"name":"lw","passwd":"456"}, {"name":"szx","passwd":"000"}, ]#帐户数据库 current_dic = {"username":None,"login":False} #当前登录状态 def auth_func(func): def wrapper(*args,**kwargs): if current_dic["username"] and current_dic["login"]:#判断当前登陆状态为真时,执行函数; res = func(*args, **kwargs) return res username = input("用户名:") passwd = input("密码:") for user_dic in user_list: #遍历帐户列表 if username == user_dic["name"] and passwd == user_dic["passwd"]: current_dic["username"] = username #更改登陆状态 current_dic["login"] = True res = func(*args,**kwargs) return res else: print("用户名或者密码错误") return wrapper @auth_func def index(): print("欢迎来到京东主页") @auth_func def home(name): print("欢迎回家%s" %name) @auth_func def shopping_car(name): print("%s购物车里有娃娃、奶茶"%(name)) index() home("老王") shopping_car("老王")
假设有代码:blog
improt time def test(): time.sleep(2) print("test is running!") test()
很显然,这段代码运行的结果必定是:等待约2秒后,输出内存
test is running
在行动以前,咱们先来看一下文章开头提到的缘由1(关于函数“变量”(或“变量”函数)的理解)
假设有代码:
x = 1 y = x def test1(): print("Do something") test2 = lambda x:x*2
那么在内存中,应该是这样的:
很显然,函数和变量是同样的,都是“一个名字对应内存地址中的一些内容”
那么根据这样的原则,咱们就能够理解两个事情:
若是这两个问题能够理解,那么咱们就能够进入到下一个缘由(关于高阶函数的理解)
那么对于高阶函数的形式能够有两种:
那么这里面所说的函数名,实际上就是函数的地址,也能够认为是函数的一个标签而已,并非调用,是个名词。若是能够把函数名当作实参,那么也就是说能够把函数传递到另外一个函数,而后在另外一个函数里面作一些操做,根据这些分析来看,这岂不是知足了装饰器三原则中的第一条,即不修改源代码而增长功能。那咱们看来一下具体的作法:
仍是针对上面那段代码:
improt time def test(): time.sleep(2) print("test is running!") def deco(func): start = time.time() func() #2 stop = time.time() print(stop-start) deco(test) #1
咱们来看一下这段代码,在#1处,咱们把test看成实参传递给形参func,即func=test。注意,这里传递的是地址,也就是此时func也指向了以前test所定义的那个函数体,能够说在deco()内部,func就是test。在#2处,把函数名后面加上括号,就是对函数的调用(执行它)。所以,这段代码运行结果是:
test is running! the run time is 3.0009405612945557
咱们看到彷佛是达到了需求,即执行了源程序,同时也附加了计时功能,可是这只知足了原则1(不能修改被装饰的函数的源代码),但这修改了调用方式。假设不修改调用方式,那么在这样的程序中,被装饰函数就没法传递到另外一个装饰函数中去。
那么再思考,若是不修改调用方式,就是必定要有test()这条语句,那么就用到了第二种高阶函数,即返回值中包含函数名
以下代码:
improt time def test(): time.sleep(2) print("test is running!") def deco(func): print(func) return func t = deco(test) #3 #t()#4 test()
咱们看这段代码,在#3处,将test传入deco(),在deco()里面操做以后,最后返回了func,并赋值给t。所以这里test => func => t,都是同样的函数体。最后在#4处保留了原来的函数调用方式。
看到这里显然会有些困惑,咱们的需求不是要计算函数的运行时间么,怎么改为输出函数地址了。是由于,单独采用第二张高阶函数(返回值中包含函数名)的方式,而且保留原函数调用方式,是没法计时的。若是在deco()里计时,显然会执行一次,而外面已经调用了test(),会重复执行。这里只是为了说明第二种高阶函数的思想,下面才真的进入重头戏。
嵌套函数指的是在函数内部定义一个函数,而不是调用,如:
def func1(): def func2(): pass 而不是 def func1(): func2()
另外还有一个题外话,函数只能调用和它同级别以及上级的变量或函数。也就是说:里面的能调用和它缩进同样的和他外部的,而内部的是没法调用的。
那么咱们再回到咱们以前的那个需求,想要统计程序运行时间,而且知足三原则。
代码:
improt time def timer(func) #5 def deco(): start = time.time() func() stop = time.time() print(stop-start) return deco test = timer(test) #6 def test(): time.sleep(2) print("test is running!") test() #7
这段代码可能会有些困惑,怎么突然多了这么多,暂且先接受它,分析一下再来讲为何是这样。
首先,在#6处,把test做为参数传递给了timer(),此时,在timer()内部,func = test,接下来,定义了一个deco()函数,当并未调用,只是在内存中保存了,而且标签为deco。在timer()函数的最后返回deco()的地址deco。
而后再把deco赋值给了test,那么此时test已经不是原来的test了,也就是test原来的那些函数体的标签换掉了,换成了deco。那么在#7处调用的其实是deco()。
那么这段代码在本质上是修改了调用函数,但在表面上并未修改调用方式,并且实现了附加功能。
把函数当作是盒子,test是小盒子,deco是中盒子,timer是大盒子。程序中,把小盒子test传递到大盒子temer中的中盒子deco,而后再把中盒子deco拿出来,打开看看(调用)
这样作的缘由是:
咱们要保留test(),还要统计时间,而test()只能调用一次(调用两次运行结果会改变,不知足),再根据函数即“变量”,那么就能够经过函数的方式来回闭包。因而乎,就想到了,把test传递到某个函数,而这个函数内恰巧内嵌了一个内函数,再根据内嵌函数的做用域(能够访问同级及以上,内嵌函数能够访问外部参数),把test包在这个内函数当中,一块儿返回,最后调用这个返回的函数。而test传递进入以后,再被包裹出来,显然test函数没有弄丢(在包裹里),那么外面剩下的这个test标签正好能够替代这个包裹(内含test())。
至此,一切皆合,大功告成,单只差一步。
根据以上分析,装饰器在装饰时,须要在每一个函数前面加上:
test = timer(test)
显然有些麻烦,Python提供了一种语法糖,即:
@timer
这两句是等价的,只要在函数前加上这句,就能够实现装饰做用。
以上为无参形式
improt time def timer(func) def deco(): start = time.time() func() stop = time.time() print(stop-start) return deco @timer def test(parameter): #8 time.sleep(2) print("test is running!") test()
对于一个实际问题,每每是有参数的,若是要在#8处,给被修饰函数加上参数,显然这段程序会报错的。错误缘由是test()在调用的时候缺乏了一个位置参数的。而咱们知道test = func = deco,所以test()=func()=deco()
,那么当test(parameter)有参数时,就必须给func()和deco()也加上参数,为了使程序更加有扩展性,所以在装饰器中的deco()和func(),加如了可变参数*agrs和 **kwargs。
完整代码以下:
improt time def timer(func) def deco(*args, **kwargs): start = time.time() func(*args, **kwargs) stop = time.time() print(stop-start) return deco @timer def test(parameter): #8 time.sleep(2) print("test is running!") test()
那么咱们再考虑个问题,若是原函数test()的结果有返回值呢?好比:
def test(parameter): time.sleep(2) print("test is running!") return "Returned value"
那么面对这样的函数,若是用上面的代码来装饰,最后一行的test()实际上调用的是deco()。有人可能会问,func()不就是test()么,怎么没返回值呢?
实际上是有返回值的,可是返回值返回到deco()的内部,而不是test()即deco()的返回值,那么就须要再返回func()的值,所以就是:
def timer(func) def deco(*args, **kwargs): start = time.time() res = func(*args, **kwargs)#9 stop = time.time() print(stop-start) return res#10 return deco
其中,#9的值在#10处返回。
完整程序为:
improt time def timer(func) def deco(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print(stop-start) return res return deco @timer def test(parameter): #8 time.sleep(2) print("test is running!") return "Returned value" test()
又增长了一个需求,一个装饰器,对不一样的函数有不一样的装饰。那么就须要知道对哪一个函数采起哪一种装饰。所以,就须要装饰器带一个参数来标记一下。例如:
@decorator(parameter = value)
好比有两个函数:
def task1(): time.sleep(2) print("in the task1") def task2(): time.sleep(2) print("in the task2") task1() task2()
要对这两个函数分别统计运行时间,可是要求统计以后输出:
the task1/task2 run time is : 2.00……
因而就要构造一个装饰器timer,而且须要告诉装饰器哪一个是task1,哪一个是task2,也就是要这样:
@timer(parameter='task1') # def task1(): time.sleep(2) print("in the task1") @timer(parameter='task2') # def task2(): time.sleep(2) print("in the task2") task1() task2()
那么方法有了,可是咱们须要考虑如何把这个parameter参数传递到装饰器中,咱们以往的装饰器,都是传递函数名字进去,而此次,多了一个参数,要怎么作呢?
因而,就想到再加一层函数来接受参数,根据嵌套函数的概念,要想执行内函数,就要先执行外函数,才能调用到内函数,那么就有:
def timer(parameter): # print("in the auth :", parameter) def outer_deco(func): # print("in the outer_wrapper:", parameter) def deco(*args, **kwargs): return deco return outer_deco
首先timer(parameter),接收参数parameter=’task1/2’,而@timer(parameter)也恰巧带了括号,那么就会执行这个函数, 那么就是至关于:
timer = timer(parameter) task1 = timer(task1)
后面的运行就和通常的装饰器同样了:
import time def timer(parameter): def outer_wrapper(func): def wrapper(*args, **kwargs): if parameter == 'task1': start = time.time() func(*args, **kwargs) stop = time.time() print("the task1 run time is :", stop - start) elif parameter == 'task2': start = time.time() func(*args, **kwargs) stop = time.time() print("the task2 run time is :", stop - start) return wrapper return outer_wrapper @timer(parameter='task1') def task1(): time.sleep(2) print("in the task1") @timer(parameter='task2') def task2(): time.sleep(2) print("in the task2") task1() task2()
至此,装饰器的所有内容结束。