流畅的Python第七章总结

流畅的Python

第七章总结

  1. 装饰器基础知识

装饰器是可调用对象,其参数是另一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。

>>> 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()。

函数装饰器会在被装饰的函数定义后就执行。
当这个模块被导入时,也会立即执行。

  1. 变量作用域规则
>>> 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字节码。

  1. 闭包

闭包指延伸了作用域的函数,其中包含函数定义体中引用,但是不在定义体中定义的非全局变量。

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]
  1. 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
  1. 标准库里的装饰器
  • 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指定是否把不同参数类型的结果分开保存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值