学到装饰器,然后总结一下理解装饰器需要的知识
一.python的作用域
当然,Python与大多数编程语言一样,搜索变量值的时候,即命名空间的规则,会采用'就近原则'.具体来说,由近及远依次为: 本地作用域(Local) --> 外部嵌套函数作用域(Enclosing Local) --> 全局/模块作用域(Global) --> 内置作用域(Built-in).
a = abs(-1) # abs:Built-in
b = 3 # b:Global
def f1():
print(b) # b:Global
a = 3 # a:Local
print(a)
def f2():
a = 3 # a:Enclosing
def f3():
b = 3 # b:Local
print(b)Local 与 Enclosing 是一个相对的概念. 在函数 f1 中, a 是一个 Local 变量, 而在 f2 中 a 是一个 Enclosing 变量.
只有模块、类、函数才会引入新的作用域. 而 if for while 语句不会引入新的作用域.
全局作用域中的变量对于下层作用域比如函数来说, 是一个只读变量.
a = 4
def foo():
a = 3
foo()
print(a) # 4
函数内部不是修改全局变量 a 的值,而是重新定义了一个本地变量 a.所以全局变量 a 的值没有改变
a = 4
def foo():
a = a + 3
foo()
# local variable 'a' referenced before assignment
在函数 foo 内部, a = a + 3 这个表达式的存在会让 Python 编译函数的定义体时,它判断 a 是局部变量,因为在函数中给它赋值了。内部作用域中要修改外部作用域变量的值时,要用 global、nonlocal 关键字声明外部作用域变量
a = 3
def foo():
global a # 使用 global 声明 a ,便可以在函数中修改 a 的值
a = 4
foo()
print(a)
# 4
def f1():
a = 3
def f2():
nonlocal a # 使用 nonlocal 声明 a, 便可以在嵌套的函数内部修改 a 的值
a = a + 1
print(a)
f2()
二.闭包与自由变量
什么是闭包?闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量
总结:闭包是个函数,它能够访问函数体之外定义的非全局变量,而这个非全局变量指的就是自由变量.
下面举一个例子, 定义一个 avg 函数,参数为一个值, 不断累加的计算从开始到现在所接收的全部值的平均值
def make_average():
series = []
def average(value):
series.append(value)
total = sum(series)
return total / len(series)
return average
avg = make_average() # 1
avg(10) # 2
# 10
avg(20) # 3
# 15
说明:1调用 make_average 返回一个 average 函数对象.这就是一个闭包函数,因为 avg 可以访问 average 函数定义体之外的 series .
注意:这里嵌套函数 average 并没有'改变' series, 只是修改它的值, 因为 series 是一个可变的列表.所以并不会报错.那么如果 series 是一个不可变对象呢? 会发生什么?
上面的例子效率比较低, 没一次都得 sum.我们难道不可以保存每一步计算的 total 吗?
def make_average():
count = 0
total = 0
def average(value):
count += 1
total += value
return total / count
return average
avg = make_average()
avg(10)
# local variable 'count' referenced before assignment
说明: 在嵌套函数 average 内部有 count += 1 ,此表达式等价为 count = count + 1.在编译阶段, 会把内部的 count 解释为一个本地变量, 所以如果没有 nonlocal 声明的话, 会报错 local variable 'count' referenced before assignment.
简单的处理当然不行, 我们需要使用 nonlocal 将 count 和 total 变成自由变量
def make_average():
count = 0
total = 0
def average(value):
nonlocal count, total
count += 1
total += value
return total / count
return average
# 开始调用.返回一个函数对象,并且这个对象是一个闭包
avg = make_average()
avg(10) # 10
avg(20) # 15
三.装饰器
装饰器用来'装饰'一个函数,为函数添加额外的功能,一般来说并不是核心功能.
装饰器接收一个函数作为参数,装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
举例, 我们定义一个装饰器,用来计算并且显示每个函数运行的时间
import time
def decorate(func):
def wrapper(*args):
to = time.perf_counter()
result = func(*args)
t1 = time.perf_counter() - to
print(f'运行时间为:{t1}')
return result
return wrapper
让我们装饰一下别的函数
@decorate
def foo(n):
i = 0
while i < n*n:
i += 1
return i
print(foo(600))
# 程序的运行时间为:0.022877089999383315
# 360000
`@decorate `是一个语法糖, 等同于 func = decorate(func), 所以此时 func 是 wrapper 函数的引用.
如何证明?
foo.__name__
# wrapper
这是一个瑕疵啊,我们需要改进.由此我们需要使用一些标准库装饰器.
使用 functools.wraps 装饰器把相关的属性(func._name 和 func.doc_)从 func 复制到 wrapper 中
from functools import wraps
import time
def decorate(func):
@wraps(func) # 将func的一些属性覆盖到wrapper中去
def wrapper(*args):
to = time.perf_counter()
result = func(*args)
t1 = time.perf_counter() - to
print(f'运行时间为:{t1}')
return result
return wrapper