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

来源:博客园 时间:2023-04-10 17:27:57
016. 请实现如下功能|谈谈你对闭包的理解

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

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

def avg(...):    passavg(10) =>返回10avg(20) =>返回10+20的平均值15avg(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 averageravg = 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 disdis(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 (", line 3>)             10 LOAD_CONST               2 ("make_average..averager")             12 MAKE_FUNCTION            8 (closure)             14 STORE_FAST               0 (averager)  6          16 LOAD_FAST                0 (averager)             18 RETURN_VALUEDisassembly of ", 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_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对象,list对象len(avg.__closure__) # 1avg.__closure__[0].cell_contents # [] 因为你还没调用avg(10)avg(20)avg(30)avg.__closure__[0].cell_contents # [10, 20, 30] 保存着真正的值

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

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

关键词:

推荐内容

Copyright 2000-2021 by www.jiaoyu.thxxww.com all rights reserved

备案号:京ICP备2021034106号-50

邮箱 : 55 16 53 8@qq.com