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

文章介绍了如何在Python中实现一个累加平均值计算的功能,首先展示了使用Average类的方式,然后重点讲解了利用闭包的概念,通过make_average函数和内部的averager函数实现相同的效果。文章还探讨了闭包的定义,以及通过dis模块和__code__属性来理解闭包的工作原理。
摘要由CSDN通过智能技术生成
  • 实现一个函数(可以不是函数)avg,计算不断增加的系列值的平均值,效果如下

     

    def avg(...): pass avg(10) =>返回10 avg(20) =>返回10+20的平均值15 avg(30) =>返回10+20+30的平均值20

  • Python常见面试题015.请实现一个如下功能的函数有点类似,但又不太一样

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

1|1类的实现方式

  • 参考代码

     

    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只在第一次初始化的时候置为空列表

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


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

1|2闭包实现

  • 参考代码

     

    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,是个函数名,所以它也能调用

1|3闭包 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 的绑定

1|4反汇编(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


1|5code属性

  • 怎么样不云里雾里呢

  • 查看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_flagsCO_* 标志的位图,详见 此处
    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] 保存着真正的值

  • 闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何时才能相遇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值