函数装饰器与闭包

装饰器基础知识

  • 两大特性:
    能把装饰的函数替换成另一个函数
    装饰器在加载模块时(导入时)立即执行,而被装饰的函数(普通函数)只有在明确调用时运行


    函数装饰器与闭包_第1张图片
    装饰器

    函数装饰器与闭包_第2张图片
    装饰器何时执行

    函数装饰器与闭包_第3张图片
    在程序运行之前,即导入时就已经执行装饰器了

    导入时执行装饰器

    这个例子中装饰器存在两个问题:
    (1)通常装饰器在一个模块定义,然后应用到另外一个模块的函数中
    (2)大多数装饰器会内部定义一个函数然后将其返回
    上述的例子很像 Django 框架中通过这样的装饰器把函数添加到某种中央注册处,如 URL 模式映射到 HTTP 响应函数注册处。这种注册装饰器原封不动地返回被装饰的函数,只是在装饰器中把被装饰的函数添加到一个列表表示这个函数注册了。

装饰器通常会定义一个内部函数,然后将它返回,而使用内部函数的代码要靠闭包才能正确运行,为了理解闭包又得先理解变量作用域

变量作用域规则

函数装饰器与闭包_第4张图片
UnboundLocalError
  • Python 在编译函数体时,它判断 b 是局部变量,因为在函数中给它赋了值,所以它会从局部作用域中获取 b 的值,而发现 b 没有绑定值。在函数体一开始 global b 就不会报错了。
  • 这是一种设计选择 ,而不是缺陷:
    Python 不要求声明变量,但假定函数体中赋值的变量是局部变量。
    JavaScript 也不要求声明变量,但要对局部变量声明(var),否则可能在不知情的情况下获取全局变量
    所以,对函数体内赋值的变量,Python 选择默认局部变量,JavaScript 选择默认全局变量

闭包

指延伸了作用域的函数


函数装饰器与闭包_第5张图片
菜鸟的做法

函数装饰器与闭包_第6张图片
较好的做法
  • 两者第一步都是返回一个可调用对象 avg。
  • 对于 series,是 make_averager 函数的局部变量,因为在函数体内初始化了。
    但当调用 avg(10) 的时候,series 不再是局部变量了,而称为自由变量(free variable)。
    自由变量:未在本地作用域绑定的变量。故 series 是未在本地作用域(averager 函数)绑定的自由变量
  • 闭包范围:从嵌套函数 averager 延伸到自由变量 series 的绑定


    函数装饰器与闭包_第7张图片
    闭包范围

    函数装饰器与闭包_第8张图片
    审查 avg 对象
  • 综上,闭包是一种函数,它会保留定义函数时已存在的自由变量的绑定 。
    这样调用函数时,虽然定义作用域不可用,但仍可以使用那些绑定
nonlocal 声明
函数装饰器与闭包_第9张图片
UnboundLocalError
  • count += 1由于 int 是不可变对象,没有 _iadd_ 方法,所以调用的是 _add_ 方法,完全等价于 count = count + 1。这会产生新对象 count
  • 对对象 count 进行了赋值,所以 Python 解析器认为这是个局部变量,count = count + 1,所会在 averager 作用域获取 count 的值,而发现 count 没有绑定值,故报 UnboundLocalError 错,只需用关键字 nonlocal 声明即可。
  • 前面的 series.append(new_value) 没有报错,这又是因为 series 是一个列表,可变对象,并没有赋值。
实现一个简单的装饰器

函数装饰器与闭包_第10张图片
装饰器

函数装饰器与闭包_第11张图片
内部函数

不足之处:
(1)不支持关键字参数
(2)屏蔽了被装饰函数的 _ name_ 和 _ doc_ 属性
functools.wraps 装饰器可以把相关属性从 func 复制到 clocked,且能正确处理关键字参数
只需对内部函数 clocked 使用 @functools.wraps(func) 即可

标准库中的装饰器

functools.lru_cache
实现备忘(memoization)功能,把耗时的函数结果保存起来,避免重复计算
lru 是 Least Recently Used 缩写,表明缓存不会无限制增长,一段时间后不用的缓存会被丢弃。

函数装饰器与闭包_第12张图片
lru_cache

lru_cache() 括号内接受配置 :

  • maxsize=128,代表存储 128 个调用结果(为得到最佳性能,应设为 2 的幂数个)
    typed=False,代表参数类型不区分,即参数 1 与 1.0 认为是一样的
  • 使用字典存储结果,因此被 lru_cache() 装饰的函数所有参数必须是可散列的

functools.singledispatch

  • 泛函数(generic function):根据参数类型,以不同方式执行某一操作的一组函数
    单分派泛函数(singledispatch):根据的是第一个参数类型
    多分派泛函数:根据的是多个参数类型
from functools import singledispatch
from collections import abc
import numbers
import html

@singledispatch
def htmlize(obj):
    content = html.escape(repr(obj))
    return '
{}
'.format(content) @htmlize.register(str) def _(text): print('传入的第一个参数是 str') @htmlize.register(tuple) @htmlize.register(abc.MutableSequence) def _(seq): print('传入的第一个参数是 tuple 或 list')
单分派泛函数
  • 注册的专门函数名称无关紧要,_ 就是一个不错选择
  • 注册的专门函数应该处理抽象基类,这样代码兼容类型更广泛。
    即用 numbers.Integral 代替 int;abc.MutableSequence 代替 list
  • 有时可以作为 if/elif/elif 的替代品

参数化装饰器

指接受参数的装饰器。
创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,最后把这个装饰器应用到被装饰的函数

registy = set()

def regitster(active=True):
    def decorate(func):
        print('running regitster active(%s), decorate(%s)' % (active, func))
        if active:
            registy.add(func)
        else:
            registy.discard(func)
        return func
    return decorate
    
@regitster()
def f1():
    print('running f1()')
    
@regitster(active=False)
def f2():
    print('running f2()')

你可能感兴趣的:(函数装饰器与闭包)