基本概念
语法糖
介绍
- 语法糖是指计算机语言中的某种特殊语法,该语法对语言的功能没有任何影响
- 语法糖不会增加或减少任何功能,它只是对程序更便捷、易读的一种写法
- 语法糖可以完全等价地转换为非原本语法糖的代码
代码示例
语法糖在不影响代码功能的情况下对初始代码进行一种更便捷的写法
1 2 3 4 5 6 7 8 9
| users = [] for i in range(1,10): if i % 2 == 0: users.append(i) print(users)
[2, 4, 6, 8]
|
1 2 3 4 5 6
| users = [i for i in range(1,10) if i % 2 == 0] print(users)
[2, 4, 6, 8]
|
闭包
介绍
- 在内部函数中对外部作用域的变量进行引用,并且一般外部函数的返回值为内部函数,那么内部函数就被称之为闭包,外部函数就被称为闭包生成函数
- 闭包中不能修改外部函数的局部变量
语法
1 2 3 4 5 6
|
def 外部函数(x): def 内部函数(y): return x + y return 内部函数
|
装饰器
介绍
- 装饰器的本质就是闭包
- 装饰器是对闭包的一种语法糖
- 装饰器通常用于装饰函数、类的方法
- 一般被装饰后的函数会在原来的基础上增加一些新功能
代码示例
被装饰的函数 = 外部函数(被装饰的函数)
等同于@外部函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def 外部函数(func): def 内部函数(*args,**kwargs): result = func(*args,**kwargs) return result return 内部函数
def 被装饰的函数(): 代码块 被装饰的函数 = 外部函数(被装饰的函数) print(被装饰的函数())
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def 外部函数(func): def 内部函数(*args,**kwargs): result = func(*args,**kwargs) return result return 内部函数
@外部函数 def 被装饰的函数(): 代码块 print(被装饰的函数())
|
基本知识
一切皆对象
在函数名后面加上()
后函数会被执行,如test()
。反之不加()
则函数不会被执行,如test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def test(): print("test") return "我是test()函数"
print("第一次输出: " + str(test)) print("----------------") print("第二次输出: " + str(test())) print("----------------")
a = test print("第三次输出: " + a())
第一次输出: <function test at 0x000001E1DC7E0F78> ---------------- test 第二次输出: 我是test()函数 ---------------- test 第三次输出: 我是test()函数
|
在函数中定义函数
Python可以在函数的代码块中定义一个或多个函数,外层的函数被称为外部函数
,内层的函数被称为内部函数
,内部函数不能在外部函数外被调用。
下方代码中外部函数为test
,内部函数有func_1
与func_2
,在外部函数的代码块中使用print
调用内部函数,在函数外的代码中使用test()
调用外部函数,则内外函数都会被执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| def test(): print("我是test()函数")
def func_1(): return "我是test函数下的func_1函数"
def func_2(): return "我是test函数下的func_2函数"
print(func_1()) print(func_2())
test()
我是test()函数 我是test函数下的func_1函数 我是test函数下的func_2函数
func_1() NameError: name 'func_1' is not defined
|
从函数中返回函数
在函数中定义函数时,并不一定要在外部函数中执行内部函数,也可以将内部函数作为外部函数的返回值进行返回
下方代码中使用传参的方式来指定要返回的函数,要注意返回的是func_1
而不是func_1()
。后者在test
函数被赋值调用时会被执行,而前者只会被传递而不会被执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| def test(name="func_1"): print("我是test()函数")
def func_1(): return "我是test函数下的func_1函数"
def func_2(): return "我是test函数下的func_2函数"
if name == "func_1": return func_1 else: return func_2
a = test() print(a) """ 结果: 我是test()函数 <function test.<locals>.func_1 at 0x000001CF7D57D558> """
b = test(name="func_2") print("函数test返回的数据类型为: " + str(type(b))) print(b) """ 结果: 我是test()函数 函数test返回的数据类型为: <class 'function'> <function test.<locals>.func_2 at 0x000001CF7D57DF78> """
print(a()) """ 结果: 我是test函数下的func_1函数 """
|
将函数作为参数传递给另一个函数
将函数A作为参数传递给函数B,在函数B中执行函数A,并且可以将函数A的返回结果进行再次运算或修饰、直接输出、返回等操作,而该操作其实就是闭包
下方代码中func_2(func_1)
将函数func_1
传递给函数func_2
,函数func_2
在传参时会被执行。而函数func_1
是在函数func_2
内部的print(func())
中被执行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def func_1(): print("当我被执行时我会率先打印") return "我是函数func_1"
def func_2(func): print("我是函数func_2") print(func())
func_2(func_1)
我是函数func_2 当我被执行时我会率先打印 我是函数func_1
|
下方代码中func_2(func_1())
将函数func_1
执行后再将执行结果传参给函数func_2
,这里要注意实参是func_1()
而不是func_1
,所以会先执行func_1
再执行func_2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def func_1(): print("当我被执行时我会率先打印") return "我是函数func_1"
def func_2(func): print("我是函数func_2") print(func)
func_2(func_1())
当我被执行时我会率先打印 我是函数func_2 我是函数func_1
|
闭包
定义闭包
下方代码中定义了一个外部函数test
,该函数运行时先打印我是外部函数test()
,再返回内部函数func_1
。而代码a=test(10)
将10传参给函数test()
,这时候函数test
会被执行且打印我是外部函数test()
,并将返回值赋值给变量a,这时候变量a就等同于内部函数func_1
,再对变量a传参时就等同于执行函数func_1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| def test(x): print("我是外部函数test()")
def func_1(y): return x + y
return func_1
a = test(10) """ 结果: 我是外部函数test() # 外部函数被执行 """
print(a) """ 结果: <function test.<locals>.func_1 at 0x000001C07D3C0EE8> # 外部函数将内部函数作为返回值返回 """
print(a(20)) """ 现在变量a就是内部函数func_1,对内部函数进行传参后内部函数返回相加后的结果 结果: 30 """
|
使用闭包
未使用闭包时
想要知道某个函数的运行时间时,就要在每个函数内重复的计算时间,这会使得代码很冗余,代码量变多,如下示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import time
def func_1(): start = time.time() time.sleep(0.2) print("我是func_1函数") end = time.time() total_time = end - start return total_time
def func_2(): start = time.time() time.sleep(0.2) print("我是func_2函数") end = time.time() total_time = end - start return total_time
def func_3(): start = time.time() time.sleep(0.2) print("我是func_3函数") end = time.time() total_time = end - start return total_time
print(func_1()) print(func_2()) print(func_3())
我是func_1函数 0.20311379432678223 我是func_2函数 0.20303797721862793 我是func_3函数 0.2140798568725586
|
使用闭包
下方代码中定义了一个外部函数get_runtime
,在外部函数中定义了一个内部函数run
,外部函数通过传参获取到需要运算执行时间的函数,内部函数负责运算外部函数通过传参进来的函数的执行时间,且返回该函数的名称与执行时间。内部函数作为外部函数的返回值被返回了,所以下方代码func_1 = get_runtime(func_1)
中变量func_1
就是内部函数run
。代码print(func_1())
就等同执行了内部函数run
,内部函数中再执行了传递进来的函数func_1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import time
def get_runtime(func): def run(): start = time.time() func() end = time.time() total_time = end - start return f"函数{func.__name__}运行时间: {total_time}" return run
def func_1(): time.sleep(0.2) print("我是func_1函数")
def func_2(): time.sleep(0.2) print("我是func_2函数")
def func_3(): time.sleep(0.2) print("我是func_3函数")
func_1 = get_runtime(func_1) func_2 = get_runtime(func_2) func_3 = get_runtime(func_3)
print(func_1()) print(func_2()) print(func_3())
我是func_1函数 函数func_1运行时间: 0.20359444618225098 我是func_2函数 函数func_2运行时间: 0.2025609016418457 我是func_3函数 函数func_3运行时间: 0.20307469367980957
|
带返回值的闭包
上述例子中被装饰的函数都没有返回值,而下方被装饰的函数中使用返回值后却没有被返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import time
def get_runtime(func): def run(): start = time.time() func() end = time.time() total_time = end - start return f"函数{func.__name__}运行时间: {total_time}" return run
def func_1(): time.sleep(0.2) print("我是func_1函数") return "Hello func_1"
func_1 = get_runtime(func_1) print(func_1())
我是func_1函数 函数func_1运行时间: 0.20908427238464355
|
在闭包中执行函数时可以将被装饰的函数的执行结果赋值给变量,再由内部函数返回该变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import time
def get_runtime(func): def run(): start = time.time() response = func() end = time.time() total_time = end - start print(f"函数{func.__name__}运行时间: {total_time}") return response return run
def func_1(): time.sleep(0.2) print("我是func_1函数") return "Hello func_1"
func_1 = get_runtime(func_1) print(func_1())
我是func_1函数 函数func_1运行时间: 0.21098947525024414 Hello func_1
|
带参数的闭包
外部函数负责接收需要被装饰的函数,内部函数负责接收需要被装饰的函数的参数。在内部函数用使用*args
与**kwargs
来接收所有的参数(算固定格式),并将该参数传入到被装饰的函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import time
def get_runtime(func): def run(*args,**kwargs): start = time.time() result = func(*args,**kwargs) end = time.time() total_time = end - start print(f"函数{func.__name__}运行时间: {total_time}") return result return run
def func_1(num1,num2): time.sleep(0.2) print("我是func_1函数") return num1 + num2
func_1 = get_runtime(func_1) print(func_1(1,1))
我是func_1函数 函数func_1运行时间: 0.21248936653137207 2
|
装饰器
定义装饰器
在基本概念中了解到装饰器就是闭包的语法糖,所以定义装饰器就类似于闭包
闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import time
def get_runtime(func):
def run(): start = time.time() func() end = time.time() total_time = end - start return f"函数{func.__name__}运行时间: {total_time}" return run
def func_1(): time.sleep(0.2) print("我是func_1函数") return "Hello func_1"
func_1 = get_runtime(func_1) print(func_1())
我是func_1函数 函数func_1运行时间: 0.2032308578491211
|
装饰器
下方代码与上方代码的运行结果一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import time
def get_runtime(func):
def run(): start = time.time() func() end = time.time() total_time = end - start return f"函数{func.__name__}运行时间: {total_time}" return run @get_runtime def func_1(): time.sleep(0.2) print("我是func_1函数") return "Hello func_1"
print(func_1())
我是func_1函数 函数func_1运行时间: 0.2032308578491211
|
带参数的被装饰函数
与带参数的闭包方法一样,只要接收传递*xx
与**xxx
即可
模板
1 2 3 4 5 6 7
| def 装饰器(func): def 内部函数(*args,**kwargs): result = func(*args,**kwargs) return result return 内部函数
|
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| def wrapper(func): """装饰器"""
def run(*args,**kwargs): """输出执行日志""" result = func(*args,**kwargs) return "结果为: " + str(result)
return run
@wrapper def add(num1, num2): """计算加法""" result = num1 + num2 return result
print(add(1, 1)) print(add(1, num2=1))
结果为: 2 结果为: 2
|
带参数的装饰器
如果装饰器函数本身也需要传递参数,则需要在装饰器外层再包裹一层函数,该函数用于接收参数供装饰器使用。这里的参数指的是装饰器的参数而不是被装饰函数的参数!
错误写法
装饰器只能接收一个参数,且该参数只能为函数类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| def wrapper(func,flag): """装饰器"""
def run(*args,**kwargs): """输出执行日志""" if flag == "outlog": print("正在计算中...") result = func(*args,**kwargs) return "结果为: " + str(result)
return run
@wrapper("outlog") def add(num1,num2): """计算加法""" result = num1 + num2 return result
print(add(1,1))
TypeError: wrapper() missing 1 required positional argument: 'flag'
|
正确写法
需要在装饰器的外层再包裹一个函数,该函数用于接收参数、返回装饰器。要注意有参数的装饰器写法为@外层函数()
而不是@外层函数
,前者带括号表示立即运行该函数,而后者不带括号表示不运行该函数(就不会返回装饰器了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| def pr_info(flag=""): """用于接收参数给装饰器wrapper使用"""
def wrapper(func): """装饰器"""
def run(*args,**kwargs): """输出执行日志""" if flag == "outlog": print("正在计算中...") result = func(*args,**kwargs) return "结果为: " + str(result)
return run
return wrapper
@pr_info() def add(num1, num2): """计算加法""" result = num1 + num2 return result
@pr_info("outlog") def sub(num1, num2): """计算减法""" result = num1 - num2 return result
print(add(1, 1)) print(sub(1, 1))
结果为: 2 正在计算中... 结果为: 0
|
接收任意数量的实参与任意数量的关键词实参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| def pr_info(flag="",*param, **kvs): """用于接收参数给装饰器wrapper使用"""
def wrapper(func): """装饰器"""
def run(*args,**kwargs): """输出执行日志""" print(param) print(kvs)
if flag == "outlog": print("正在计算中...") result = func(*args,**kwargs) return "结果为: " + str(result)
return run
return wrapper
@pr_info("outlog","a","b",color="red") def add(num1, num2): """计算加法""" result = num1 + num2 return result
print(add(1, 1))
('a', 'b') {'color': 'red'} 正在计算中... 结果为: 2
|
装饰类
装饰类
上方示例中都是使用函数来装饰函数,该处通过函数类装饰整个类。写完装饰器后只需要在需要被装饰的类上加@装饰器
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| def wrapClass(cls): """定义一个装饰器""" def run(*args,**kwargs): print('类名:', cls.__name__) return cls(*args,**kwargs) return run
@wrapClass class Test: def __init__(self, num1, num2): self.num1 = num1 self.num2 = num2
def fun(self): """进行加法运算""" print(self.num1 + self.num2)
m = Test(1,1) m.fun()
类名: Test 2
|
装饰类方法
装饰类方法与装饰类差不多,直接在需要被装饰的方法上加@装饰器
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| def WrapFunc(func): """定义一个装饰器""" def run(*args,**kwargs): print('函数名:', func.__name__) return func(*args,**kwargs) return run
class Test: def __init__(self, num1, num2): self.num1 = num1 self.num2 = num2
@WrapFunc def add(self): """进行加法运算""" print(self.num1 + self.num2)
def sub(self): """进行减法运算""" print(self.num1 - self.num2)
m = Test(1,1) m.add() m.sub()
函数名: add 2 0
|
多装饰器的执行顺序
当一个函数被多个装饰器装饰时,装饰顺序由下至上,调用顺序由上至下
下方使用两个装饰器装饰一个函数时,等同于如下闭包。函数会先被wrapper2
给装饰后再被wrapper1
装饰,调用时会先执行wrapper1
的内部函数再执行后wrapper2
的内部函数。
1 2 3 4 5 6 7 8 9 10 11 12
| @wrapper1 @wrapper2 def test(): xxx
test = wrapper1(wrapper2(test))
test = wrapper2(test) test = wrapper1(test)
|
对于多装饰器的解决办法就是把装饰器改为非语法糖的闭包,如下所示(建议对下方代码进行断电调试,方便理解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| def wrapper1(func): """装饰器1""" print("我是wrapper1的外部函数")
def run_1(): print("我是wrapper1的内部函数") func() print("我是wrapper1的内部函数B") return run_1
def wrapper2(func): """装饰器2""" print("我是wrapper2的外部函数")
def run_2(): print("我是wrapper2的内部函数") func() print("我是wrapper2的内部函数B") return run_2
def test(): """被装饰的函数""" print("我是被装饰的函数")
if __name__ == "__main__":
test = wrapper2(test) test = wrapper1(test) test() """ 结果: 我是wrapper2的外部函数 我是wrapper1的外部函数 我是wrapper1的内部函数 我是wrapper2的内部函数 我是被装饰的函数 我是wrapper2的内部函数B 我是wrapper1的内部函数B
运行流程: 1. wrapper2先对test函数进行装饰,所以wrapper2的代码块就被执行了 test = wrapper2(test) print("我是wrapper2的外部函数") # 结果 --> 我是wrapper2的外部函数 2. wrapper2执行打印操作后就返回run_2函数,现在test就是run_2函数 (wrapper2执行结束) return run_2 3. wrapper1对test进行装饰(也就是run_2函数),所以wrapper1的代码块就被执行了 (wrapper1执行结束) test = wrapper1(test) print("我是wrapper1的外部函数") # 结果 --> 我是wrapper1的外部函数 4. wrapper1执行打印操作后就返回run_1函数,现在test就是run_1函数,run_1函数装饰run_2函数 return run_1 5. test()执行了run_1函数的代码块,也就是run_1函数的打印输出 test() print("我是wrapper1的内部函数") # 结果 --> 我是wrapper1的内部函数 6. run_1函数执行打印操作后,就执行run_1装饰的函数run_2 func() print("我是wrapper2的内部函数") # 结果 --> 我是wrapper2的内部函数 7. run_2函数执行打印操作后,就执行run_2装饰的函数test的代码块 (test执行结束) func() print("我是被装饰的函数") # 结果 --> 我是被装饰的函数 8. test函数执行打印操作后,就执行run_2剩下的代码,也就是打印输出 (run_2执行结束) print("我是wrapper2的内部函数B") # 结果 --> 我是wrapper2的内部函数B 9. run_2函数执行打印操作后,就执行run_1剩下的代码,也就是打印输出 (run_1执行结束) print("我是wrapper1的内部函数B") # 结果 --> 我是wrapper1的内部函数B 10. run_1执行结束后程序结束 """
|
类装饰器
定义类装饰器
介绍
类装饰器指的是使用类来定义装饰器,而不是用函数装饰器来装饰类。类装饰器一般使用内部的__call__()
方法来定义
装饰函数
下方使用类定义了一个装饰器,该装饰器用于返回传入函数的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Wrapper: """类装饰器""" def __init__(self,func): self.func = func
def __call__(self,*args,**kwargs): print("函数名: %s" % self.func.__name__) return self.func(*args,**kwargs)
@Wrapper def test(num1,num2): """被类装饰器装饰的函数""" return num1 + num2
print(test(1,1))
函数名: test 2
|
装饰类
下方使用类定义了一个装饰器,该装饰器用于返回传入类的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Wrapper: """类装饰器""" def __init__(self,cls): self.cls = cls
def __call__(self,*args,**kwargs): print("类名: %s" % self.cls.__name__) return self.cls(*args,**kwargs)
@Wrapper class Test: """被类装饰器装饰的类""" def __init__(self,num1,num2): self.num1 = num1 self.num2 = num2
def add(self): """加法运算""" print(self.num1 + self.num2)
test = Test(1,1) test.add()
类名: Test 2
|
应用场景
统计运行次数
统计某个函数的运行次数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| def wrapper(func): """用于统计函数执行次数的装饰器"""
def run(): """输出执行日志""" run.count += 1 print("%s函数被执行: %s次" % (func.__name__,run.count)) func() run.count = 0 return run
@wrapper def test(): """模拟跑步""" print("开始跑步")
test() test() test() test()
test函数被执行: 1次 开始跑步 test函数被执行: 2次 开始跑步 test函数被执行: 3次 开始跑步 test函数被执行: 4次 开始跑步
|
统计函数运行时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import time
def get_runtime(func): def run(): start = time.time() func() end = time.time() total_time = end - start return f"函数{func.__name__}运行时间: {total_time}" return run
@get_runtime def func_1(): time.sleep(0.2) print("我是func_1函数") return "Hello func_1"
print(func_1())
我是func_1函数 函数func_1运行时间: 0.20908427238464355
|
写入日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import time
def write_log(func): """写入日志""" def run(): start = time.time() result = func() end = time.time() total_time = end - start
log = { 'func': func.__name__, 'runtime': total_time, 'return': result } print("日志写入中.....") print("写入完成.....") print(log)
return log return run
def func_1(): time.sleep(0.2) print("我是func_1函数") return "Hello func_1"
func_1 = write_log(func_1) func_1()
我是func_1函数 日志写入中..... 写入完成..... {'func': 'func_1', 'runtime': 0.2035982608795166, 'return': 'Hello func_1'}
|
本文参考
https://www.runoob.com/w3cnote/python-func-decorators.html
https://blog.csdn.net/xiemanr/article/details/72510885
https://blog.csdn.net/weixin_33711647/article/details/92549215
https://mp.weixin.qq.com/s/7R7QsV5BflBk01-F8WMcBw