美文网首页Fluent Python
函数装饰器与闭包

函数装饰器与闭包

作者: 一块大番薯 | 来源:发表于2017-12-30 09:51 被阅读24次

装饰器基础知识

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


    装饰器
    装饰器何时执行
    在程序运行之前,即导入时就已经执行装饰器了
    导入时执行装饰器

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

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

变量作用域规则

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

闭包

指延伸了作用域的函数


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


    闭包范围
    审查 avg 对象
  • 综上,闭包是一种函数,它会保留定义函数时已存在的自由变量的绑定 。
    这样调用函数时,虽然定义作用域不可用,但仍可以使用那些绑定
nonlocal 声明
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 是一个列表,可变对象,并没有赋值。
实现一个简单的装饰器
装饰器
内部函数
不足之处:
(1)不支持关键字参数
(2)屏蔽了被装饰函数的 _name_ 和 _doc_ 属性
functools.wraps 装饰器可以把相关属性从 func 复制到 clocked,且能正确处理关键字参数
只需对内部函数 clocked 使用 @functools.wraps(func) 即可

标准库中的装饰器

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

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 '<pre>{}</pre>'.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()')

相关文章

  • chapter7 函数式编程

    闭包 匿名函数 装饰器 偏函数

  • 装饰器

    1.装饰器的概念 装饰器是一个闭包:内层函数引用外层函数的变量(参数也算变量),然后返回内层函数,就是闭包。装饰器...

  • Python 中的闭包

    外部函数返回内部函数简称闭包。闭包是装饰器的基础,装饰器就是用于改变原来函数状态,方法的函数。因为函数可接受的参数...

  • Python装饰器-专题笔记

    学会装饰器,Python更进阶 函数作用域到闭包到装饰器讲解,及闭包和装饰器的运用。 [√] 慕课网Meshare...

  • 2020-012 python闭包与装饰器

    python闭包与装饰器 闭包 函数和对其周围状态(lexical environment,词法环境)的引用捆绑在...

  • Python笔记四 装饰器

    装饰器 = 高阶函数+函数嵌套+闭包 高阶函数 = 参数 or 返回值为函数 函数嵌套 = 在函数中定义函数 闭包...

  • python装饰器理解

    阅读顺序: 函数引用 函数闭包 装饰器(最好先阅读上面的两个) 1.函数引用 2.函数闭包 3.装饰器 背景:一般...

  • 区别普通嵌套函数、闭包、装饰器

    1.普通嵌套函数: 2.闭包: 闭包,本质是个函数,即调用了自由变量x的函数inner 3.装饰器: 装饰器是一个...

  • Python 2 - 高级用法 - 装饰器

    Python 2 - 高级用法 - 装饰器 一谈到 装饰器,就离不开闭包 闭包 闭包就是能够读取其他函数内部变量的...

  • 装饰器

    装饰器的作用:在不改变原函数的情况下给函数增加功能! 装饰器由闭包和语法糖组成。 闭包 即两个函数嵌套,外部函数返...

网友评论

    本文标题:函数装饰器与闭包

    本文链接:https://www.haomeiwen.com/subject/nspigxtx.html