流畅的Python
第七章总结
- 装饰器基础知识
装饰器是可调用对象,其参数是另一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
>>> def deco(func):
def inner():
print('running inner()')
return inner
>>> @deco
def target():
print('running target()')
>>> target()
running inner()
>>> target
<function deco.<locals>.inner at 0x000002213F1BD378>
>>> @deco
def target():
print('running target()')
这段代码的作用相当于
>>> def target():
print('running target()')
>>> target = deco(target)
target函数作为参数被引用。return inner(),所以此时target正在引用inner()。
函数装饰器会在被装饰的函数定义后就执行。
当这个模块被导入时,也会立即执行。
- 变量作用域规则
>>> b = 6
>>> def f2(a):
print(a)
print(b)
b = 9
>>> f2(3)
3
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
f2(3)
File "<pyshell#5>", line 3, in f2
print(b)
UnboundLocalError: local variable 'b' referenced before assignment
之所以会报错是因为python判断b是局部变量,因为在函数中给它赋了值。python不要求声明变量,但是会假定在函数中赋值的变量为局部变量。如果想要变成全局变量。要使用global声明。
>>> b = 6
>>> def f3(a):
global b
print(a)
print(b)
b = 9
>>> f3(3)
3
6
>>> b
9
可以使用dis模块中的dis来反编译python字节码。
- 闭包
闭包指延伸了作用域的函数,其中包含函数定义体中引用,但是不在定义体中定义的非全局变量。
class Averager():
def __init__(self):
self.series = []
def __call__(self, new_value):
self.series.append(new_value)
total = sum(self.series)
return total/len(self.series)
ave = Averager()
print(ave(10))
print(ave(11))
print(ave(12))
10.0
10.5
11.0
Averager类的实例ave在self.series中存储历史值。
def makeAverager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
a = makeAverager()
print(a(10))
print(a(11))
print(a(12))
而闭包函数中,当调用makeAverager()时,这时函数已经返回,也就意味着局部变量seriers被回收,但是却能使用历史值,在averager函数中,series是自由变量。
可以在函数对象的__code__属性中找到自由变量和局部变量。
print(a.__code__.co_varnames)
print(a.__code__.co_freevars)
('new_value', 'total')
('series',)
而series绑定在返回的avg函数的__closure__属性中,这个属性的各个元素对应着各个自由变量的名称,而这些元素是cell对象,可以有cell_contents属性查看真正的值。
print(a.__closure__)
print(a.__closure__[0].cell_contents)
(<cell at 0x0000019DFCB1A858: list object at 0x0000019DFCB8FE48>,)
[10, 11, 12]
- nonlocal声明
对于上一个方法,如果只保存当前的总和和个数而不是保存所有的历史值,效率会更高。
def makeAverager():
count = 0
total = 0
def averager(new_value):
count += 1
total += new_value
return total/count
return averager
但是这个函数有问题,其实在vscode中写完这个函数,没有运行,vscode已经报错了,说averager中的count和total没有定义,而makeAverager中的count和total没有使用。如果运行的话。
UnboundLocalError: local variable ‘count’ referenced before assignment
就会报这个错误。这是因为上一个例子中series是一个列表,只是调用了append()函数,并没有进行赋值,是可变对象,而数字是不可变对象。因此,count和total不是自由变量。
def makeAverager():
count = 0
total = 0
def averager(new_value):
nonlocal count,total
count += 1
total += new_value
return total/count
return averager
a = makeAverager()
print(a(10))
print(a(11))
print(a(12))
10.0
10.5
11.0
一个例子:
import time
def clock(func):
def clocked(*args):
#t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() #返回当时的计算机系统时间
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
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 factorail(6)')
print('6! = ', factorial(6))
结果为:
**************************************** Calling snooze(.123)
[0.00000030s] snooze(0.123) -> None
**************************************** Calling factorail(6)
[0.00090310s] factorial(1) -> 1
[0.00111160s] factorial(2) -> 2
[0.00138960s] factorial(3) -> 6
[0.00171500s] factorial(4) -> 24
[0.00206200s] factorial(5) -> 120
[0.00232340s] factorial(6) -> 720
6! = 720
- 标准库里的装饰器
- functools.lru_cache
对于以下代码:
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci((n - 2))
print(fibonacci(6))
结果如下:
[0.00002850s] fibonacci(1) -> 1
[0.00041000s] fibonacci(0) -> 0
[0.00080300s] fibonacci(2) -> 1
[0.00106510s] fibonacci(1) -> 1
[0.00137370s] fibonacci(3) -> 2
[0.00161270s] fibonacci(1) -> 1
[0.00189440s] fibonacci(0) -> 0
[0.00217050s] fibonacci(2) -> 1
[0.00247330s] fibonacci(4) -> 3
[0.00277070s] fibonacci(1) -> 1
[0.00304570s] fibonacci(0) -> 0
[0.00328350s] fibonacci(2) -> 1
[0.00358360s] fibonacci(1) -> 1
[0.00389470s] fibonacci(3) -> 2
[0.00444600s] fibonacci(5) -> 5
[0.00481670s] fibonacci(1) -> 1
[0.00507540s] fibonacci(0) -> 0
[0.00536500s] fibonacci(2) -> 1
[0.00567650s] fibonacci(1) -> 1
[0.00594790s] fibonacci(3) -> 2
[0.00625100s] fibonacci(1) -> 1
[0.00651440s] fibonacci(0) -> 0
[0.00677550s] fibonacci(2) -> 1
[0.00698000s] fibonacci(4) -> 3
[0.00727640s] fibonacci(6) -> 8
8
可见每次迭代都要计算很多重复的数值,效率很低,使用lru_cache效率快很多。
@functools.lru_cache()
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci((n - 2))
print(fibonacci(6))
这段代码需import functools,结果如下:
[0.00002920s] fibonacci(1) -> 1
[0.00035360s] fibonacci(0) -> 0
[0.00069760s] fibonacci(2) -> 1
[0.00089160s] fibonacci(3) -> 2
[0.00120670s] fibonacci(4) -> 3
[0.00145250s] fibonacci(5) -> 5
[0.00166360s] fibonacci(6) -> 8
8
这样的效果相当于Fibonacci先被clock装饰,返回的函数再被lru_cache()装饰。lru_cache装饰的效果就是利用字典存储每次函数执行的结果,类似缓存机制。
functools.lru_cache(maxsize = 128, typed = False) maxsize指定了存储多少个结果,当缓存满了,旧的结果会被扔掉。typed指定是否把不同参数类型的结果分开保存。