Python中的函数装饰器和闭包

导语:本文章记录了本人在学习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__ 方法即可改写参数化装饰器。具体实现留作勤劳的你去课后思考,感兴趣的可以与我私信交流。

    原文作者:Hanwencheng
    原文地址: https://segmentfault.com/a/1190000014244546
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞