016. 请实现如下功能|谈谈你对闭包的理解

摘自<流畅的python> 第七章 函数装饰器和闭包

  • 实现一个函数(可以不是函数)avg,计算不断增加的系列值的平均值,效果如下

    def avg(...):
    pass
    avg(10) =>返回10
    avg(20) =>返回10+20的平均值15
    avg(30) =>返回10+20+30的平均值20
  • Python常见面试题015.请实现一个如下功能的函数有点类似,但又不太一样

  • 关键是你需要有个变量来存储历史值

类的实现方式

  • 参考代码

    class Average():
    def __init__(self):
    self.series = []
    def __call__(self, value):
    self.series.append(value)
    return sum(self.series)/len(self.series) avg = Average()
    print(avg(10))
    print(avg(20))
    print(avg(30))
  • avg是个Average的实例

  • avg有个属性series,一开始是个空列表

  • __call__使得avg对象可以像函数一样调用

  • 调用的时候series会保留,因为series只在第一次初始化的时候置为空列表

  • 下面的事情就变得简单了


  • 但有没有其他做法呢?
  • 有的,答案是:闭包

闭包实现

  • 参考代码

    def make_average():
    series = []
    def averager(value):
    series.append(value)
    return sum(series)/len(series)
    return averager
    avg = make_average()
    print(avg(10))
    print(avg(20))
    print(avg(30))
  • 仔细对比2个代码,你会发现相似度是极高的

  • 一个是类,一个是函数

  • 类中存储历史值的是self.series,函数中的是series局部变量

  • 类实例能调用是实现了__call__,函数的实现中,avg是make_average()的返回值averager,是个函数名,所以它也能调用

闭包 closure 初识

  • 闭包closure定义:

    • 在一个外函数中定义了一个内函数
    • 内函数里运用了外函数的临时变量
    • 外函数的返回值是内函数的引用
  • 以上面的为例

    def make_average(): # 外函数
    series = [] # 临时变量(局部变量)
    def averager(value): # 内函数
    series.append(value)
    return sum(series)/len(series)
    return averager # 返回内函数的引用
  • 下面这些话你可能听的云里雾里的,姑且听一下。

  • series 是 make_averager 函数的局部变量,因为那个函数的定义体中初始化了series:series = []

  • 调用 avg(10) 时,make_averager 函数已经返回了,而它的本地作用域也一去不复返了

  • 在 averager 函数中,series 是自由变量(free variable)。这是一个技术术语,指未在本地作用域中绑定的变量

  • averager 的闭包延伸到那个函数的作用域之外,包含自由变量 series 的绑定

反汇编(dis=Disassembler)

from dis import dis
dis(make_average)
  2           0 BUILD_LIST               0
2 STORE_DEREF 0 (series) 3 4 LOAD_CLOSURE 0 (series)
6 BUILD_TUPLE 1
8 LOAD_CONST 1 (<code object averager at 0x000002225DD1CBE0, file "<ipython-input-1-a43a8601eedd>", line 3>)
10 LOAD_CONST 2 ('make_average.<locals>.averager')
12 MAKE_FUNCTION 8 (closure)
14 STORE_FAST 0 (averager) 6 16 LOAD_FAST 0 (averager)
18 RETURN_VALUE Disassembly of <code object averager at 0x000002225DD1CBE0, file "<ipython-input-1-a43a8601eedd>", line 3>:
4 0 LOAD_DEREF 0 (series)
2 LOAD_METHOD 0 (append)
4 LOAD_FAST 0 (value)
6 CALL_METHOD 1
8 POP_TOP 5 10 LOAD_GLOBAL 1 (sum)
12 LOAD_DEREF 0 (series)
14 CALL_FUNCTION 1
16 LOAD_GLOBAL 2 (len)
18 LOAD_DEREF 0 (series)
20 CALL_FUNCTION 1
22 BINARY_TRUE_DIVIDE
24 RETURN_VALUE
  • 读懂上面的,不是人干的事情,不过你依然有可能

    https://docs.python.org/zh-cn/3/library/dis.html#bytecodes

code属性

  • 怎么样不云里雾里呢

  • 查看avg.__code__属性

    [_ for _ in dir(avg.__code__) if _[:2]=='co']
    ['co_argcount',
    'co_cellvars',
    'co_code',
    'co_consts',
    'co_filename',
    'co_firstlineno',
    'co_flags',
    'co_freevars',
    'co_kwonlyargcount',
    'co_lnotab',
    'co_name',
    'co_names',
    'co_nlocals',
    'co_posonlyargcount',
    'co_stacksize',
    'co_varnames']
  • 官方解释

    属性 描述
    co_argcount 参数数量(不包括仅关键字参数、* 或 ** 参数)
    co_code 原始编译字节码的字符串
    co_cellvars 单元变量名称的元组(通过包含作用域引用)
    co_consts 字节码中使用的常量元组
    co_filename 创建此代码对象的文件的名称
    co_firstlineno 第一行在Python源码的行号
    co_flags CO_* 标志的位图,详见 此处
    co_lnotab 编码的行号到字节码索引的映射
    co_freevars 自由变量的名字组成的元组(通过函数闭包引用)
    co_posonlyargcount 仅限位置参数的数量
    co_kwonlyargcount 仅限关键字参数的数量(不包括 ** 参数)
    co_name 定义此代码对象的名称
    co_names 局部变量名称的元组
    co_nlocals 局部变量的数量
    co_stacksize 需要虚拟机堆栈空间
    co_varnames 参数名和局部变量的元组
  • 通过__code__分析

    def make_average():
    series = []
    def averager(value):
    series.append(value)
    total = sum(series)
    return total/len(series)
    return averager
    avg = make_average()
    avg.__code__.co_varnames # 参数名和局部变量的元组
    # ('value', 'total') # value是参数,total是局部变量名
    avg.__code__.co_freevars
    # ('series',) # 自由变量的名字组成的元组(通过函数闭包引用)
  • 结合avg.__closure__

    avg.__closure__
    # (<cell at 0x000002225FA4DC70: list object at 0x000002225EE35600>,)
    # 这是个cell对象,list对象
    len(avg.__closure__) # 1
    avg.__closure__[0].cell_contents # [] 因为你还没调用
    avg(10)
    avg(20)
    avg(30)
    avg.__closure__[0].cell_contents # [10, 20, 30] 保存着真正的值
  • 闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。

  • 只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量

Python常见面试题016. 请实现如下功能|谈谈你对闭包的理解的更多相关文章

  1. 整理的最全 python常见面试题(基本必考)

    整理的最全 python常见面试题(基本必考) python 2018-05-17 作者 大蛇王 1.大数据的文件读取 ① 利用生成器generator ②迭代器进行迭代遍历:for line in ...

  2. 整理的最全 python常见面试题

      整理的最全 python常见面试题(基本必考)① ②③④⑤⑥⑦⑧⑨⑩ 1.大数据的文件读取: ① 利用生成器generator: ②迭代器进行迭代遍历:for line in file; 2.迭代 ...

  3. 315道Python常见面试题

    第一部分,Python基础篇 为什么学习Python? 通过什么途径学习的Python? Python和Java.PHP.C.C#.C++等其他语言的对比? 简述解释型和编译型编程语言? Python ...

  4. Python常见面试题

    Q 1:Python 有哪些特点和优点? 作为一门编程入门语言,Python 主要有以下特点和优点: ● 可解释● 具有动态特性● 面向对象● 简明简单● 开源● 具有强大的社区支持当然,实际上 Py ...

  5. python常见面试题(三)

    问题1 到底什么是Python?你可以在回答中与其他技术进行对比(也鼓励这样做). 答案 下面是一些关键点: Python是一种解释型语言.这就是说,与C语言和C的衍生语言不同,Python代码在运行 ...

  6. python常见面试题(mark)

    1.大数据的文件读取 ① 利用生成器generator ②迭代器进行迭代遍历:for line in file 2.迭代器和生成器的区别 1)迭代器是一个更抽象的概念,任何对象,如果它的类有next方 ...

  7. python常见面试题(二)

    1. 到底什么是Python?你可以在回答中与其他技术进行对比(也鼓励这样做). 下面是一些关键点: Python是一种解释型语言.这就是说,与C语言和C的衍生语言不同,Python代码在运行之前不需 ...

  8. python常见面试题(一)

    1.Python是如何进行内存管理的? 答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制 一.对象的引用计数机制 Python内部使用引用计数,来保持追踪内存中的对象,所有对象都 ...

  9. 【python常见面试题】之python 中对list去重的多种方法

    在python相关职位的面试过程中,会对列表list的去重进行考察.(注意有时会要求保证去重的顺序性) 1.直观方法 li=[1,2,3,4,5,1,2,3] new_li=[] for i in l ...

  10. 10道Python常见面试题

    1.MySQL索引种类 1.普通索引 2.唯一索引 3.主键索引 4.组合索引 5.全文索引 2.索引在什么情况下遵循最左前缀的规则? 最左前缀原理的一部分,索引index1:(a,b,c),只会走a ...

随机推荐

  1. java 与 JSON

    Java 与 JSON JSON 是不同程序之间传递信息的一种格式.本文描述了 JSON 在 Java 中的应用. 目前 Java 中比较主流的相关解析工具有谷歌提供的 Gson 和阿里提供的 Fas ...

  2. Offer 提速:如何写出有亮点的简历

    先来个灵魂拷问: 你与他人相比,有什么能形成明显区分度的优势条件? 这里有两个层面的问题,一是 如何识别出你的优势条件,毕竟大多数人大多数时候可能都是在做业务,临到写简历的时候要求总结日常工作中跟别人 ...

  3. 链表反转,C++实现

    1 // To Compile and Run: g++ invert_list.cc -std=c++11 -Wall -O3 && ./a.out 2 3 4 #include & ...

  4. HTML基础知识学习

    一.HTML概述 1.系统结构: ①.B/S架构 Browser Server(浏览器/服务器的交互形式.) Browser支持哪些语言:HTML CSS Javascript S是服务器端Serve ...

  5. win10启动和安装nacos服务

    https://blog.csdn.net/tbmingzhao/article/details/113276845

  6. QT数据结构内存分配策略

    在QT的Reference中无意看到了QString及其他类型数据结构内存的分配策略,翻译并记录一下. 在QString的数据结构中,QString通过一次附加一个字符来动态构建字符串.假设我们向QS ...

  7. SQL SERVER 内存优化表踩坑记

    背景 因为生产应用需要刷新大量的状态数据到数据库中用于客户端的显示,于是新建了一张状态表,表内行数固定,状态更新到列内.刚开始运行时还行,更新都很及时,查询速度也不慢.于是就这样使用了有一个月的时间. ...

  8. gensim

    官方文档: https://radimrehurek.com/gensim/models/word2vec.html 1.训练模型定义 from gensim.models import word2v ...

  9. XML的定义以及XML的编写

     什么是XML? 定义 1. XML 是 EXtensible Markup Language 这个单词的简写,中文意思就是:可扩展标记语言. a)可扩展:html 标签是预先定义好的,不能任意定义, ...

  10. Sql Server代理作业、定时任务

    需求: 本次需求为每15分钟获取一次思路为,创建结果表,代理作业定时更新数据并存入结果表,后端只需要调用结果表数据数据,如果超期不同的天数则给出不同的提示信息,因为没有触发点,所以用到了本文内容. 右 ...