导语:本文章记录了本人在学习Python基础之函数篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。
本文重点:
1、掌握装饰器的本质、功能和特点;
2、了解闭包的概念以及Python变量调用规则;
3、了解并学会使用标准库中重要的装饰器;
4、掌握参数化装饰器的意义和代码实现方式。
一、装饰器基础知识
装饰器功能(decorator):将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数。
装饰器本质:是一个返回函数的高阶函数。
装饰器特点:
1、多数装饰器会把被装饰的函数替换成其他函数
2、函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时执行。
装饰器有时采用嵌套函数表示的原因(个人理解):
一些装饰器的装饰功能只有在被装饰函数被调用时方可触发,因此需要用嵌套函数的形式来编写。
二、自由变量、闭包与nonlocal声明
自由变量(free variable):指未在本地作用域绑定的变量,介于局部变量和全局变量之间。
变量查找规则:在python中, 一个变量的查找顺序是 LEGB (L:Local 局部环境,E:Enclosing 闭包,G:Global 全局,B:Built-in 内建).
闭包:引用了自由变量的函数。
闭包的作用:
- 闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数,此时即便生成闭包的环境(父函数)已经释放,闭包仍然存在。
- 这个过程很像类(父函数)生成实例(闭包),
不同的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时创建,一般程序执行完毕后作用域才释放。
- 因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活。
nonlocal声明:可以将局部变量声明为自由变量。
eg:计算移动平均值的高阶函数:
def averager():
sum=0
n=0
def avg(i):
nonlocal sum,n
sum+=i
n+=1
return print(sum/n)
return avg
a=averager()
a(3)
a(5)
a(7)
输出分别是3,4,5
三、装饰器进阶使用
1.标准库中的装饰器
Python内置了三个用于装饰方法的函数:property,classmethod和staticmethod
三个重要的内置装饰器:
- functools.wraps:
(1)协助构建行为良好的装饰器。
(2)可以把被装饰对象的相关属性复制到装饰器中
,默认有 __module__、__name__、__doc__。
(3)个人理解,装饰器在实现装饰的过程中意见把被装饰函数替换了。此时你想调用被装饰函数的__doc__和__name__会发现是none,此时通过functools.wraps就可以在装饰时把相关属性复制过来使用,避免这个问题发生。 - functools.lru_cache
(1)实现备忘功能,即缓存,避免发生重复调用来提高效率。
(2)注意调用时必须带括号
,因为此装饰器包含maxsize和typed两个参数,带括号表示使用默认参数。否则 functools.lru_cache不清楚该如何执行。 - functools.singledispatch
为函数提供重载功能。被其装饰的函数会成为泛函数(generic function):根据第一个参数的类型,用不同的方式执行相同操作的一组函数。
以functools.lru_cache为例实现斐波那契函数的计时装饰器:.
import time
import functools
def clock(func):
@functools.lru_cache()#减少重复自引用,避免重复计算
def clocked(arg):
t0=time.perf_counter()
func(arg)
result=func(arg)
t1=time.perf_counter()
processtime=t1-t0
name=func.__name__
print("[{0:.8f}] {1}({2})={3}".format(processtime,name,arg,result))
return result#非常重要,否则破坏原fibs函数导致无法递归调用。
return clocked
@clock
def fibs(n):
if n<2:
return 1
else:
return fibs(n-1)+fibs(n-2)
fibs(6)#输出8
2.叠放装饰器
如同函数可以嵌套使用,装饰器亦可以叠放起来装饰同一对象。语法如下:
@d1
@d2
def f1():
pass
上述装饰操作等价于d1(d2(f)),故易知叠放在上端的装饰器靠后执行。
3.参数化装饰器
普通装饰器的进阶版,通过参数接口可以更方便的定制我们需要的装饰器,适应需求变化。代码实现较普通装饰器多嵌套一层函数,用来传递用户输入的参数。
以计时器举例形式如下:
import time
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
def clock(fmt=DEFAULT_FMT): #三层函数来实现
def decorate(func):
def clocked(*_args):
t0 = time.time()
_result = func(*_args)
elapsed = time.time() - t0
name = func.__name__
args = ', '.join(repr(arg) for arg in _args)
result = repr(_result)
print(fmt.format(**locals()))
return _result
return clocked
return decorate
if __name__ == '__main__':
@clock()
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)
输出:
[0.12480044s] snooze(0.123) -> None
[0.13660240s] snooze(0.123) -> None
[0.12480044s] snooze(0.123) -> None
4.类装饰器
上边实现参数化装饰器的代码由于包含三重嵌套略显复杂, 事实上复杂的装饰器用 class 实现更方便。通过 __call__ 方法即可改写参数化装饰器。具体实现留作勤劳的你去课后思考,感兴趣的可以与我私信交流。