目录
一、装饰器的概念和意义
1、装饰器的概念
- 装饰器:用来装饰其他函数,即为其他函数添加特定功能的函数。
- 装饰器 = 高阶函数 + 嵌套函数
- 装饰器函数的两个基本原则:
- 装饰器不能修改被装饰函数的源码。
- 装饰器不能修改被装饰函数的调用方式。
- 学习装饰器之前必须掌握的概念:函数即变量、高阶函数、嵌套函数。
2、函数即变量
-
函数既可以直接被调用,也可以作为变量进行赋值。
-
函数名跟变量名一样,只是一个变量的标识符,它指向函数定义对应的内存地址。
-
例如:
- boo赋值给a后,a也能调用boo函数。
pythondef boo(): print("in boo") a=boo boo() # in boo a() # in boo
-
任何变量名都是指向变量值的内存地址,如果把存放一个变量值的空间看成是一间屋子的话,那么这间屋子里存放的就是变量的值,而变量名则是屋子上的门牌号。
-
对于函数也是一样的道理,函数的本质是一串字符串,这串字符串会保存在内存空间中,函数名是指向这个内存空间的地址,也相当于一个门牌号。
-
在函数定义中去调用其他函数时,并不会立即调用该函数。
-
例如:
*pythondef foo(): print("in foo") boo() def boo(): print("in boo") foo() # 不会报错
-
在执行一个调用了其他函数的函数时,如果在内存中还没有找到被调用函数的定义,则程序会报错。
-
例如:
*pythondef foo(): print("in foo") boo() foo() # 会报错 def boo(): print("in boo")
3、高阶函数
-
符合下列条件之一的函数就是高阶函数:
- 接受函数名作为形参
- 返回值中包含函数名
-
例如:
*pythondef foo(): print("in foo") def gf(func): # 高阶函数 print(func) # 运行foo函数之前附加的功能语句 func() gf(foo)
-
尝试使用高阶函数统计任意函数运行时间。
pythonimport time def foo(): print('in foo') def gf(func): start_time=time.time() # 记录函数开始运行的时间 func() end_time=time.time() print('运行func的时间为:',end_time-start_time) gf(foo) # 改变了foo的调用方式,不满足装饰器的第二个原则,说明其不是一个真正的装饰器
- 【注】这并不是一个装饰器。该方式确实没有改变函数的源码,但是却改变了函数的调用方式。
-
再尝试不改变调用方式能不能实现该功能。
*pythonimport time def foo(): print('in foo') def gf(func): start_time=time.time() return func end_time=time.time() print('运行func的时间为':end_time-start_time) foo=gf(foo) foo()
- 【注】该方式虽然没有改变函数的调用方式,但是函数在return时就已经结束了,不能实现计时的功能。
-
高阶函数的两个条件对编写装饰器的意义:
- 接受函数名作为形参(不改变被装饰函数的代码的前提下增加功能)
- 返回值中包含函数名(不改变被装饰函数的调用方式)
4、嵌套函数
-
通过def关键字定义在另一个函数中的函数叫嵌套函数。
-
例如:
pythondef foo(): print('in foo') def boo(): print('in boo')
-
尝试加入嵌套函数统计任意函数运行时间。
*pythonimport time def foo(): print('in foo') def timer(func): def gf(): start_time=time.time() # 记录函数开始运行的时间 func() end_time=time.time() print('运行func的时间为:',end_time-start_time) return gf foo=timer(foo) foo() # 实际调用的是gf函数
- 此时,函数的源码以及调用方式都没有改变。
5、编写装饰器
-
基本套路:
- 定义一个接受函数名作为参数的高阶函数。
- 在高阶函数中定义一个嵌套函数,在该嵌套函数中封装想要添加的功能代码,调用作为参数传入的函数名,返回嵌套函数的函数名。
-
例如:编写一个装饰器,要求使用该装饰器能够统计任意函数运行时间。
- 我们已经在上面实现了装饰器的基本原理,现在只需要修改装饰器的表示方式即可。
pythonimport time def timer(func): def gf(): start_time=time.time() # 记录函数开始运行的时间 func() end_time=time.time() print('运行func的时间为:',end_time-start_time) return gf @timer # foo=timer(foo),Python的语法糖,一种语法简化方式 def foo(): print('in foo') foo()
-
@timer就是一个装饰器。
二、装饰器的常见类型和编写
1、被装饰函数带参数
-
当被装饰函数有一个参数时,需要为嵌套函数也添加一个参数,代码如下:
*pythonimport time def timer(func): def gf(name): start_time=time.time() func(name) end_time=time.time() print('运行func的时间为:',end_time-start_time) return gf @timer # foo=timer(foo) def foo(name): print('in foo',name) #foo=gf() foo("hhh") # gf(name)
-
如果有多个参数呢,是不是每次都需要修改嵌套函数呢?我们可以为嵌套函数设定一个不定量参数。
*pythonimport time def timer(func): def gf(*args,**kwargs): # 通过提供不定量参数来自适应被装饰函数的参数 start_time=time.time() func(*args,**kwargs) # 不定量参数 end_time=time.time() print('运行func的时间为:',end_time-start_time) return gf @timer # foo=timer(foo) def foo(name,age): print('in foo',name,age) #foo=gf() foo("hhh",22) # gf(*args,**kwargs)
- 【注】(*args,**kwargs)就是不定量参数的表示。
-
模板:
*python# 被装饰函数带有参数或不带参数 def deco(func): def inner(*args,**kwargs): # 包含所有要附加的功能 func(*args,**kwargs) return inner
2、装饰器本身带参数
-
装饰器本身带参数时,需要再添加一层嵌套函数。
*pythonimport time def timer(timer_type): print(timer_type) def outer(func): # 加入一层嵌套函数,并且接受被装饰函数名作为参数 def inner(*args,**kwargs): start_time=time.time() func(*args,**kwargs) end_time=time.time() print('运行func的时间为:',end_time-start_time) return inner return outer @timer(timer_type='minites') # foo=timer(timer_type='minites')(foo) def foo(name,age): print('in foo',name,age) foo("hhh",22) # inner(*args,**kwargs)
-
模板:
*python# 装饰器本身带参数 def deco1(parma): # param是装饰器本身的参数 def outer(func): # 以被装饰的函数名作为参数 def inner(*args,**kwargs): # 包含所有要附加的功能 func(*args,**kwargs) return inner return outer
3、被装饰函数带返回值
-
被装饰函数带返回值时,需要在嵌套函数中返回它的值。
*pythonimport time def timer(timer_type): print(timer_type) def outer(func): def inner(*args,**kwargs): start_time=time.time() res=func(*args,**kwargs) # 保存返回值 end_time=time.time() print('运行func的时间为:',end_time-start_time) return res # 返回 return inner return outer @timer(timer_type='minites') # foo=timer(timer_type='minites')(foo) def foo(name,age): print('in foo',name,age) return name # 被装饰函数带有返回值 print(foo("hhh",22)) # inner(*args,**kwargs)
-
模板:
*python# 被装饰函数带返回值 def deco2(parma): # param是装饰器本身的参数 def outer(func): # 以被装饰的函数名作为参数 def inner(*args,**kwargs): #包含所有要附加的功能 result = func(*args,**kwargs) # 接收到被装饰函数的返回值 return result # 返回被装饰函数的返回值 return inner return outer
三、函数闭包
-
我们先来看一下什么情况需要使用闭包。
*pythonfunc_list = [] for i in range(3): # i=0,1,2 def myfunc(a): return i+a # 会受外部改变的影响 func_list.append(myfunc) for f in func_list: print(f(1)) # 预估结果 # 1,2,3 # 实际结果 # 3,3,3
-
可以看到,我们想到存储myfunc函数的运行环境,但是它的运行环境会受到外部变量i的影响。这个时候我们就需要使用到闭包。
-
我们对代码稍作修改。
*pythonfunc_list = [] for i in range(3): # i=0,1,2 def deco(i): # 接收i作为参数 def myfunc(a): return i+a #此时i为myfunc的自由变量 return myfunc # 返回myfunc函数 func_list.append(deco(i)) for f in func_list: print(f(1)) # 预估结果 # 1,2,3 # 实际结果 # 1,2,3
-
-
闭包的作用:
- 可以用来在一个函数与一组私有变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性(保存运行环境与变量的状态)。
-
闭包的特征:
- 必须要有函数的嵌套,而且外层函数必须返回内层函数。外层函数相当于给内层函数提供了一个包装起来的运行环境,在这个包装的运行环境里面,内层函数可完全自己掌握自由变量的值。
- 内层函数一定要用到外层函数中定义的自由变量。
四、闭包和装饰器的区别
|-----|-------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
| | 装饰器 | 闭包 |
| 相同的 | 1、都是函数的嵌套,分为外层函数和内层函数,而且外层函数要返回内层函数 2、代码实现的逻辑大同小异 3、二者都可以实现增加额外功能的目的 ||
| 不同点 | 1、外层函数成为装饰器 2、装饰器的外层函数主要是提供被装饰函数的引用 3、装饰器的外层函数不一定要提供变量 4、装饰器的目的:为被装饰函数提供额外的功能 5、从形式上看,闭包是装饰器的子集 | 1、外层函数称为闭包 2、闭包的外层函数主要是为了提供自由变量 3、闭包的外层函数必须提供自由变量,否则闭包无意义 4、闭包的目的是保存函数运行环境和局部变量值 |