乐趣区

闭包与装饰器

一、闭包

1. 定义

​ 闭包指延伸了作用于的函数, 其中包含函数定义体中的引用、但是不在定义体中定义的非全局变量。函数是不是匿名没有关系,关键是它能访问定义体之外定义的非全局变量。

​ 闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些变量。(只有嵌套在其他函数中的函数才可能需要处理不在全局作用于中的外部变量。)


人话:闭包就是根据不同的配置信息得到不同的结果(代码示例)。

​ 闭包是由函数和与其相关的引用环境组合而成的实体。

2. 代码示例

def fo_out(int_out):
    def fo_in(int_in):
        return int_in + int_out
    return fo_in
 
a = fo_out(1)
b = fo_out(1000)
 
print a(1)
print b(1000)

输出:2
2000

3. 注意点

  • 如果定义体之外的变量为可变类型的(如:列表),则可以用 append 传递值。

    def make_averager():
        series=[]
        
        def averager(new_value):
            series.append()
            total=sum(series)
            return total/len(series)
           
        return averager
  • 如果定义体之外的变量为不可变类型(数字、字符串、元组等),可以在定义体中声明 nonlocal,使其变成 自由变量

    def make_averager():
        count=0
        total=0
        
        def averager(new_value):
            nonlocal count,total
            count += 1
            total += new_value
            return total/count
           
        return averager

OK,让我们把 闭包 放一边,再看一下 装饰器 是什么?


二、装饰器

1. 定义

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

典型行为:把被装饰的函数替换成新函数,二者接受相同的参数,而且(通常)返回被装饰的函数本该返回的值,同时还会做一些额外的操作。

2. 代码示例

@clock
def factorial(n):
    return 1 if n<2 else n*factorial(n-1)

以上等价于:

def factorial(n):
    return 1 if n<2 else n*factorial(n-1)

factorial=clock(factorial)

可以看出,装饰器其实就是闭包的简略写法!

3. 装饰器何时运行?

registry = []


def register(func):
    print('running register(%s)' % func)
    registry.append(func)
    return func


@register
def f1():
    print('running f1()')


@register
def f2():
    print('running f2()')


if __name__ == '__main__':
    print(u'开始了')
    f1()

运行结果为:

running register(<function f1 at 0x000001F93DED7678>)
running register(<function f2 at 0x000001F93DED7708>)
开始了
running f1()

结论:即使上面的代码当做模块,在其他 py 文件中被 import,装饰器也会 立即运行!而被装饰的函数,则很正常,只有在明确调用的时候才会运行。


4. 标准库中常用的装饰器

  • lru_cache(备忘功能装饰器)

    定义

    functools.lru_cache 是非常实用的装饰器,它实现了备忘功能。这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算

    实例
    import functools
    import time
    
    
    def clock(func):
        def clocked(*args):  #注:单 *:将所有参数以元组的形式导入
            t0 = time.perf_counter()
            result = func(*args)
            elapsed = time.perf_counter() - t0
            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
    
    @functools.lru_cache()
    @clock # 其他装饰器
    def fibonacci(n):
        if n < 2:
            return n
        return fibonacci(n-2) + fibonacci(n-1)
    
    if __name__=='__main__':
        print(fibonacci(6))
  • singledispatch(单分派泛函数)

    定义

    @singledispatch装饰器可以把整体方案拆分成多个模块,甚至可以为你无法修改的类提供专门的函数。使用 @singledispatch 装饰的普通函数会变成 泛函数:根据第一个参数的类型,以不同方式执行相同操作的一组函数。


待续。。。

退出移动版