Function Decorators and Closures

装饰器是用于增强函数的行为,理解它,就必须先理解闭包。

Python3引入关键字nonlocal,如果要理解闭包,就必须了解它的所有方面,包括nonlocal的使用。

闭包在装饰器中有用,另一个用处是回调方式的异步编程和函数方式编程的风格的基础。

首先是decorator的基本知识。

  • 如何计算decorator syntax
  • 判断变量是否是local
  • closure的存在和工作原理
  • nonlocal能解决什么?

通州

decorator

本质是一个语法糖。它的参数是另外的函数。因此能把被装饰的函数替换成其他函数。

另外,在加载模块时,decorator会立即执行。(包括作为脚本输出)。而被装饰的函数,只有在明确调用时才立刻执行。

真实环境下的decorator,一般会定义一个内部函数,在这个内部函数处理传入的函数,最后返回的也是内部函数。

因此,要理解closure和 变量作用域

Variable Scope Rules

例子:

>>> b = 6
>>> def f2(a):
... print(a)
... print(b)
... b = 9
...
>>> f2(3)
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f2
UnboundLocalError: local variable 'b' referenced before assignment

错误❌的翻译:局部变量b,在被分配之前,就被引用了。

可以通过模块dis反编译模块,查看它的内部过程:

>>> import dis
>>> dis.dis(f2)
2 0 LOAD_GLOBAL 0 (print)
2 LOAD_FAST 0 (a)
4 CALL_FUNCTION 1
6 POP_TOP 3 8 LOAD_GLOBAL 0 (print)
10 LOAD_FAST 1 (b)
12 CALL_FUNCTION 1
14 POP_TOP 4 16 LOAD_CONST 1 (9)
18 STORE_FAST 1 (b)
20 LOAD_CONST 0 (None)
22 RETURN_VALUE

LOAD_FAST(var_num)实际上是将指向局部对象co_varnames[var_num]的引用推入栈顶。

>>> f2.__code__.co_varnames
('a', 'b')

print(b)这行代码,要打印的b是局部变量,即f2内部定义的变量,但并没有声明赋值,就引用所以报告❌。

分析:

这是因为,在把源码转化为字节码时,b = 9决定了b是一个局部变量。因此上面的反编译代码显示b是一个局部变量。

所以在实际调用函数f2时,执行到第2行代码print(b),Python会尝试从函数的本地环境获取b,并发现b没有绑定值,最后报告错误。

我个人叫他作编译中的冲突。

去掉b = 9这行代码,那么源码转化为字节码时,根据作用域规则,局部作用域没有b,那么就往上一层级去找b, 因此编译程序会判定b为全局变量:

  4           0 LOAD_GLOBAL              0 (print)
2 LOAD_FAST 0 (a)
4 CALL_FUNCTION 1
6 POP_TOP 5 8 LOAD_GLOBAL 0 (print)
10 LOAD_GLOBAL 1 (b)
12 CALL_FUNCTION 1
14 POP_TOP
16 LOAD_CONST 0 (None)
18 RETURN_VALUE

执行函数后,会从外部作用域获得b。

如果就要保留b = 9这行代码,那么需要在函数体第一行声明:

global b

这会明确告诉编译程序,b是一个全局变量,再看dis.dis(f2):

  5           0 LOAD_GLOBAL              0 (print)
2 LOAD_FAST 0 (a)
4 CALL_FUNCTION 1
6 POP_TOP 6 8 LOAD_GLOBAL 0 (print)
10 LOAD_GLOBAL 1 (b)
12 CALL_FUNCTION 1
14 POP_TOP 7 16 LOAD_CONST 1 (11)
18 STORE_GLOBAL 1 (b)
20 LOAD_CONST 0 (None)
22 RETURN_VALUE

而后b= 9则是对全局变量b的值的修改。

根本原因:

这是Python设计的一种选择: 默认假定函数体内被赋值的变量都是局部变量。

所以运行上面的代码才会报告❌。


Closures

历史上,闭包概念和lambda匿名函数同时出现。在此之前很少有在函数内部嵌套函数定义的。

所以,很多人把闭包和匿名函数的概念混淆了。

实际上,一个closure是一个扩展了作用领域的函数。函数是否匿名和闭包没有关系,关键是函数可以访问它定义体外的nonglobal变量(当然也可以访问global变量)。

⚠️Ruby的函数定义则不同,它的设计原则是,封闭作用域。即和外面完全分开。

>>> def make_averager():
... series = []
... def averager(new_value):
... series.append(new_value)
... total = sum(series)
... return total/len(series)
... return averager
...
>>> avg = make_averager()
>>> avg
<function make_averager.<locals>.averager at 0x103a5cc10>

make_averager函数内声明了一个局部变量series,当make_averager()执行并返回值后,它的局部作用领域就关闭了。

因此,avg实际上从makd_averager复制了一个series变量,由于series并不是averager函数体内声明的变量,可以称series为自由变量free variable。

使用co_freevars属性可以看到自由变量。

>>> avg.__code__.co_varnames
('new_value', 'total')
>>> avg.__code__.co_freevars
('series',)

series的绑定在avg的闭包__closure__属性内。以cell对象的类型储存。

>>> avg.__closure__
(<cell at 0x10395fcd0: list object at 0x103a652c0>,)
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg.__closure__[0]
<cell at 0x10395fcd0: list object at 0x103a652c0>
>>> avg.__closure__[0].cell_contents
[10, 11]

因此,闭包就是指扩展了作用领域的函数。一般特指被嵌套的函数。

nonlocal声明

python3的新增语法糖。为被嵌套的函数提供变量的支持。

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

本章后面的章节:

  • 简单的装饰器。
  • 标准库的装饰器:内置函数: property, classmethod, staticmethod.
  • functools.lru_cache
  • 叠放装饰器
  • 参数化装饰器

《流畅的Python》Data Structures--第7章 colsure and decorator的更多相关文章

  1. 【Python学习笔记】Coursera课程《Python Data Structures》 密歇根大学 Charles Severance——Week6 Tuple课堂笔记

    Coursera课程<Python Data Structures> 密歇根大学 Charles Severance Week6 Tuple 10 Tuples 10.1 Tuples A ...

  2. 《Python Data Structures》Week5 Dictionary 课堂笔记

    Coursera课程<Python Data Structures> 密歇根大学 Charles Severance Week5 Dictionary 9.1 Dictionaries 字 ...

  3. 《Python Data Structures》 Week4 List 课堂笔记

    Coursera课程<Python Data Structures> 密歇根大学 Charles Severance Week4 List 8.2 Manipulating Lists 8 ...

  4. 《流畅的Python》 第一部分 序章 【数据模型】

    流畅的Python 致Marta,用我全心全意的爱 第一部分 序幕 第一章 Python数据模型 特殊方法 定义: Python解释器碰到特殊句法时,使用特殊方法激活对象的基本操作,例如python语 ...

  5. 流畅的python学习笔记:第二章

    第二章开始介绍了列表这种数据结构,这个在python是经常用到的结构 列表的推导,将一个字符串编程一个列表,有下面的2种方法.其中第二种方法更简洁.可读性也比第一种要好 str='abc' strin ...

  6. 流畅的python学习笔记:第一章

    这一章中作者简要的介绍了python数据模型,主要是python的一些特殊方法.比如__len__, __getitem__. 并用一个纸牌的程序来讲解了这些方法 首先介绍下Tuple和nametup ...

  7. 《流畅的Python》 A Pythonic Object--第9章

    Python的数据模型data model, 用户可以创建自定义类型,并且运行起来像内建类型一样自然. 即不是靠继承,而是duck typing. 支持用内建函数来创建可选的对象表现形式.例如repr ...

  8. 流畅的python学习笔记第七章:装饰器

    装饰器就如名字一样,对某样事物进行装饰过后然后返回一个新的事物.就好比一个毛坯房,经过装修后,变成了精装房,但是房子还是同样的房子,但是模样变了. 我们首先来看一个函数.加入我要求出函数的运行时间.一 ...

  9. 『流畅的Python』第1~4章笔记_数据结构、编码

    由于1~4章内容零散且基础,所以统计一下涉及到的内容,记录一下,方便查阅(第一张图右键新页面打开即可看到清晰大图)

随机推荐

  1. thinkphp5 验证器 validate 和 layer

    首先tp5的验证器使用特方便 设置规则即可通用 首先页面html(layer 配合) 毕竟是后端 尽量用一些成熟的前台框架  之前用boostrap $.ajax({ url:'/index/Regi ...

  2. web前端常用meta整理

    标签提供关于HTML文档的元数据.元数据不会显示在页面上,但是对于机器是可读的.它可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 web 服务. 页面关键词 <meta ...

  3. [学习笔记] Blender 常用工具 移动与旋转,缩放, 变换

    点击上面的移动图标之后,可在X,Y,Z轴移动物体. shift+S 之后,可有更多的移动选项. 旋转:可沿X,Y, Z 进行旋转 缩放 还可输入缩放的具体数值,更精确. 变换 可同时做移动.旋转.缩放 ...

  4. 工作总结 CTO(张王岩) File构造器

    import java.io.File; /** * 构建File对象 * @author Allen17805272076 * */ public class FileDemo2 { public ...

  5. [转帖]AIDA64 6.10版发布:全面支持中国兆芯、海光x86 CPU

    AIDA64 6.10版发布:全面支持中国兆芯.海光x86 CPU https://www.cnbeta.com/articles/soft/892877.htm 支持国产x86了 作为硬件识别工具领 ...

  6. 值得收藏的Python第三方库

    网络站点爬取 爬取网络站点的库Scrapy – 一个快速高级的屏幕爬取及网页采集框架.cola – 一个分布式爬虫框架.Demiurge – 基于PyQuery 的爬虫微型框架.feedparser ...

  7. SASS摘要

    SASS提高了代码的重复利用率,提高了效率.不用去记或者查找复杂或者繁琐的参数和书写规则. 这里简单整理了实际业务中常常用到的几个功能. 1. 父元素引用 & a { &:hover ...

  8. WUSTOJ 1208: 计算整数四则运算表达式的结果(Java)

    1208: 计算整数四则运算表达式的结果 参考资料 数据结构(C语言版)严蔚敏 吴伟民 编著----表达式求值 题目   简单四则运算.更多内容点击标题. 保证表达式合法. 运算符只包含:加(+),减 ...

  9. Scratch教程:谁是真悟空

    在西游记中,有一集是“真假悟空”,六耳猕猴变成了悟空的模样与真悟空真假难辨,打的不可开交. 在Scartch中,我们常常会使用一个本体来生成多个克隆体,这在开发过程中有重要的意义.但在实际操作中,每个 ...

  10. 关于UBOOT,LINUX内核编译,根文件系统的15个小问题

    (1)内核默认运行地址和加载地址在哪里设置? 由 arch/arm/kernel/vmlinux.lds.S 生成的 arch/armkernel/vmlinux.lds决定   (2)从FLASH什 ...