-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdecoratorsTest.py
171 lines (148 loc) · 10 KB
/
decoratorsTest.py
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
"""
装饰器:装饰器是一种可调用对象,其中参数是另一个函数名,这个被传入的函数就是被装饰函数,他返回的也必须要是函数对象引用。用@后加函数名表示用了装饰器,下方的函数就是被装饰的函数
1. 装饰器是@后面跟着函数名这个东西,而不带下面的被装饰函数。被装饰函数单独调用的时候,和上面的装饰器其实并没有关系
2. 装饰器导入即运行,运行的时候,传入的就是下面这个被装饰函数。而被装饰函数则要显示调用才能运行,用装饰器修饰了的被装饰函数,调用这个被装饰函数的时候,已经调用的不是它本身了,而是调用的装饰器返回的函数对象(这里我一开始是理解错的)。
3. 其实一句话,当前函数调用另一个函数作为参数,这种方法也常用,但是他不是装饰器。因为装饰器必须要返回一个函数。
4. 而如果返回的就是传入的函数本身,那么他是注册装饰器
5. 但是大部分的装饰器,并不直接返回被传入的函数,而是会修改传入的函数,也就是被装饰的函数。做法就是在装饰器函数内部定义一个函数,以此来取代被装饰的函数.
6. 那这样的话,就要明确了:装饰器是个可调用对象,他传入的是函数,返回的也是函数。但是装饰器返回被装饰函数本身是没有多大意义的(注册装饰器),而是在内部定义一个函数,这个函数
调用了被装饰函数,在这个函数中执行被装饰函数,同时,这个函数返回的是个值,随便是什么:函数,表达式等等类型都可以。而这个函数外部返回这个函数
那这样其实又有个东西了,因为传入参数的原因,python中实际会创建一个a = func的赋值,那么这个a,也就是func就是个闭包中的非局部变量。
7. 因此,又可以总结几点了:装饰器解释,装饰器的运行,被装饰函数调用时的运行流程。
接下来,了解下变量:
1. 局部变量:函数体内被定义,赋值,在函数体内使用的变量
2. 全局变量:在模块中被定义,作用于整个模块。如果在函数体内用赋值语句对改变量赋值,那在函数体内,该变量为局部变量,并不使用该全局变量。
3. 自由变量:函数体内使用,赋值,但在外部被绑定,在内部属于未被绑定的变量。也就是说,在一个函数体内定义,该函数体内又有其他的函数,内层函数中又实用或者用赋值语句改变了该变量,
该变量需要在外层函数体内进行定义声明,也叫绑定。那么该变量也叫自由变量
这里又要注意了:在内层函数中只是使用,但是不会被定义,也就是说不会被重新赋值,如果在内层函数中又进行了重新赋值的话
而闭包用的就是这个自由变量。局部变量和全局变量,我都懒得写代码,这他么不是太熟?
接下来学一下闭包。
6. 闭包:也是一个函数,这个函数延伸了作用域,也包括其内层函数主体中引用的非全局变量和局部变量。这些变量必须来字包含这个内层函数体的外层函数的局部作用域。
所以说,闭包是一个函数,它保留了定义函数时存在的自由变量的绑定。那这样,调用函数时,虽然定义作用于不可用了,但是仍然可以使用这些绑定。
这样,就可以开始学习真正的装饰器了。
"""
print("------------------------1. 简单了解下装饰器,及其解释------------------------------------")
# 1. 尝试定义这个
# 这是一个装饰器函数,他传入的是一个函数名,而不传入其他参数了
def multiXY(addXY):
return addXY+2
# 这是一个被装饰的函数,他可以按照正常函数进行执行,可以正常传参。
def addXY(x, y):
return x+y
func = addXY(2, 3)
print(multiXY(func))
# 现在我要修改成装饰器的模式
print("+++++++++++++++++++++++")
def multiXY2(addXY2):
print("调用multiXY2")
return addXY2
def multiXY3(addXY2):
print("调用multiXY3")
def addXY3(x, y):
return addXY2(x, y) + 2
return addXY3
@multiXY2 # 导入模块时,便
def addXY2(x, y):
return x+y
@multiXY3
def addXY4(x, y):
return x+y
print("+++++++++++++++++++++++++++")
print(addXY2(2, 3)) # 定义了装饰器的函数。这里其实就不明白了,那这样的
print(addXY4(2, 3))
"""
阶段性总结:
可以看出,两个被装饰函数是一模一样的,但是因为装饰器的不同,他的实际输出不同,
实际上在执行addXY4()的时候,相当于执行:
func = multiXY3(addXY4).而这个实际上是将addXY4这个被装饰函数传入multiXY3这个装饰器中,然后返回在函数体内新定义的函数addXY3,而这个addXY4其实就是属于这个闭包的自由变量
所以,func == multiXY3(addXY4) == addXY3 == addXY4+2
因此执行addXY4(2, 3),实际上返回的是不考虑装饰器修饰的,addXY4(2, 3) + 2
"""
# 那么其实这里就有问题出现了。按照我之前的理解,装饰器是一个可调用对象,他传入的参数是下面被修饰的函数.
# 所以再次明确:装饰器是一个可调用对象,是@后面的部分,他在被导入模块时就被执行。至于被修饰函数,单独执行被修饰
# 函数时,他和装饰器并无关系。
# 说明:上面这个明确部分,是我理解错了,实际上,显示调用被修饰函数的时候,执行的不是纯被修饰函数,而是被装饰器改变了函数引用的那个函数或者表达式,
# 具体流程看上面三引号的黄字说明
print("-------------------------2. 闭包-------------------------------")
# 需求:不断增加系列值,要计算加一个之后的平均值
# 要我写,那么我会创建一个全局的列表,去保存每次调用时传入的值. 不能在函数体内维持series,因为每次都要重新调用函数,那series中的值无法保存
series = []
def my_Average(val):
series.append(val)
print(sum(series)/len(series))
return sum(series)/len(series)
my_Average(10)
my_Average(12)
my_Average(6)
my_Average(7)
my_Average(3)
my_Average(8)
# 如果要用闭包呢?
def make_Average():
series1 = []
def my_Average(val):
series1 = [] # 如果又在内层函数中进行了重新赋值,那么其实又被重新定义成当前函数的局部变量了,并非自由变量。但是,有些不可变类型,需要在内层函数进行重新赋值的时候,需要用nonlocal非局部变量,将他声明为自由变量
series1.append(val)
print(sum(series1) / len(series1))
return sum(series1) / len(series1)
return my_Average
avg = make_Average()
avg(10)
avg(12)
avg(6)
avg(7)
# print(series1),这个局部变量,不能被输出,因为超出了其作用于=域
# 以上两种方法都是可以的:但是问题是,在使用avg(10)时,make_Average()函数已经有了返回值,本来series这个变量就应该销毁了,但是python的机制会在函数的属性中存储该值,会维持这么一个变量
# 怎么知道函数对象中哪些是自由变量,哪些是局部变量呢?有方法的:
print(avg.__code__.co_varnames) # 查找局部变量,查找全局变量
print(avg.__code__.co_freevars) # 查找自由变量
print("---------------------------3. 简单的装饰器----------------------------------")
import time
import functools
def clock(func):
@functools.wraps(func)
# print("导入即运行")
def clocked(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ','.join(repr(args) for arg in args)
print(f'[{elapsed:0.8f}s] {name}({arg_str}) -> {result!r}')
return result
return clocked
# 我在调用时呢
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n<2 else n*factorial(n-1)
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(n)')
print('6!=', factorial(6))
print(snooze.__name__) # 返回内部实际的函数引用,其实是clocked.加入@functools.wrap之后,返回的仍然是snooze,不再是clocked了
print(snooze.__doc__) # 存储类文档的属性
"""
解释一下这段代码的运行流程:
1. 装饰器导入即运行,不是导入,那就是创建即运行,代码解释到哪里,就运行到哪里,所以会先执行clock,但是由于clocked中未传参,因此,clock函数内的clocked函数实际不执行,返回None
于是此时只输出“导入即运行”
2. 在调用snooze(.123)被装饰函数的时候,实际上已经被装饰器函数转换成clocked()的函数引用,因此,实际上是在执行clocked()
clocked函数主要是获取当前被调用函数的执行时间,所以输出执行snooze函数的执行时间
3. factorial(6)这是个递归调用, 执行流程不叙述了
其实可以发现,clock这个装饰器函有缺点存在,就是只能传入函数,不支持传入关键字参数,而且,被装饰函数的__name__以及__doc__属性都没了
那如何解决这两个问题呢:
1. 使用@functools.wrap(func),将其放在装饰器函数中,作为装饰器内部函数的装饰器使用,这样,被装饰函数的name属性得以保存
2. 要想传入关键字参数,应当使用装饰器工厂函数
何为装饰器工厂函数:
1).接受装饰器函数想接收,却不能直接接收的变量
2)。返回这个装饰器。所以定义模版为:
def 装饰器工厂函数(想要被接受的关键字参数):
def 装饰器函数(被装饰函数名):
函数体,其函数体中使用了关键字参数
也使用了被装饰函数
一般在函数体内再定义一个函数,这个函数可以返回任何东西
return 某个函数名:就是其中定义的内部函数的名字
return 装饰器函数名
"""