016. 请实现如下功能|谈谈你对闭包的理解
实现一个函数(可以不是函数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的实例
__call__
使得avg对象可以像函数一样调用
下面的事情就变得简单了
- 但有没有其他做法呢?
- 有的,答案是:闭包
闭包实现
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 初识
- 在一个
外函数
中定义了一个内函数
- 内函数里运用了外函数的
临时变量
- 外函数的返回值是
内函数的引用
-
以上面的为例
def make_average(: # 外函数 series = [] # 临时变量(局部变量 def averager(value: # 内函数 series.append(value return sum(series/len(series return averager # 返回内函数的引用
-
下面这些话你可能听的云里雾里的,姑且听一下。
-
调用 avg(10 时,make_averager 函数已经返回了,而它的本地作用域也一去不复返了
反汇编(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] 保存着真正的值
-
闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。
-
只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量
怎么样不云里雾里呢