闭包指延伸了作用域的函数,其中包含函数定义体中引用,但是不是定义体中定义的非全局变量。
1、函数要求:avg函数,作用是计算不断增长的系列值的均值;例如,整个历史中某个商品的平均收盘价。每天都会增加新价格,因此平均值要考虑至目前为止所有的价格。
>>>avg(10) 10.0 >>>avg(20) 15.0 >>>avg(30) 20.0
2、实现方式1:使用类实现
class Averager(): def __init__(self): self.series = [] def __call__(self, new_value): self.series.append(new_value) total = sum(self.series) return total/len(self.series) avg = Averager() print(avg(10)) #10.0 print(avg(20)) #15.0 print(avg(30)) #20.0
函数说明:
1、定义一个类变量保存价格,avg = Averager() 类的实例化,生成实例对象时,创建self.series,是一个数组。
2、__call__ 魔法方法:使得 Averager类的 avg 实例对象变成了可调用对象。
3、每次调用 avg(arg) 时,类变量的列表都增加一个 arg 的元素,返回 列表总和的平均值。
3、实现方式2:使用高阶函数实现(把函数作为参数传入,这样的函数称为高阶函数)
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager avg = make_averager() print(avg.__code__.co_varnames) #(‘new_value‘, ‘total‘) print(avg.__code__.co_freevars) #(‘series‘,) print(avg(10)) #10.0 print(avg(20)) #15.0 print(avg(30)) #20.0 print(avg.__closure__) #(<cell at 0x00000151B2C415E8: list object at 0x00000151B2DA7AC8>,) print(avg.__closure__[0].cell_contents) #[10, 20, 30]
函数说明:
1. 调用 make_averager() 时,返回一个 averager 函数对象。每次调用 averager 时,它会把参数添加到系列值中,然后计算平均值。
2. series 是 make_averager 函数的局部变量
4、方法1和方法2的相通之处和不同之处
相通:
调用 Averager() 或 make_averager() 都得到一个可调用对象 avg,它会更新历史值,然后计算当前均值。
我们只需要调用 avg(n) ,把n放入到系列值中,然后重新计算均值。
不同:
Averager 类的实例 avg 在 self.series 中存储历史值;
make_averager 函数在哪里寻找 series ?series 是 make_averager 函数的局部变量,因为那个函数的定义体中初始化了series, series = [] 。可是调用avg(10)时,make_averager 函数已经返回了,它的本地作用域也一去不复返了。
# 整个部分称为闭包 series = [] def averager(new_value): series.append(new_value) # series 称为自由变量 total = sum(series) return total/len(series)
说明:
averager 的闭包延伸到那个函数的作用域之外,包含自由变量的绑定。
print(avg.__code__.co_varnames) #(‘new_value‘, ‘total‘) 将函数参数和局部变量以元组的形式返回
print(avg.__code__.co_freevars) #(‘series‘,) 返回内部函数中引用外部函数参数,即自由变量(通过函数闭包引用)
更详细的说明参考:
https://blog.csdn.net/jpch89/article/details/86764245
# 示例2中 print(avg.__closure__) #(<cell at 0x00000151B2C415E8: list object at 0x00000151B2DA7AC8>,) print(avg.__closure__[0].cell_contents) #[10, 20, 30] 或者遍历得到: [cell.cell_contents for cell in func.__closure__]
说明:
1. Python 3 已将名为func_X 的函数属性重命名为使用 __X__ 形式,从而在函数属性名称空间中为用户定义的属性释放了这些名称。
例如,func_closure,func_code,func_defaults,func_dict,func_doc,func_globals,func_name
分别重命名为__closure__,__code__,__defaults__,__dict__,__doc__,__globals__,__name__。
2. 其实闭包函数相对于普通函数会多出一个closure的属性,里面定义了一个元组用于存放所有的cell对象,每个cell对象一一保存了这个闭包中所有的外部变量。
data model将__closure__定义为:None或包含该函数的自由变量绑定的单元格元组。
如果__closure__不是None,它将创建一个字典,将__code__.co_freevars中的每个单元格名称映射到元组中的相应cell.cell_contents。
3. “Cell”对象用于实现由多个作用域引用的变量。
对于每个这样的变量,一个“Cell”对象为了存储该值而被创建;引用该值的每个堆栈框架的局部变量包含同样使用该变量的对外部作用域的“Cell”引用。
访问该值时,将使用“Cell”中包含的值而不是单元格对象本身。 这种对“Cell”对象的非关联化的引用需要支持生成的字节码;访问时不会自动非关联化这些内容。
“Cell”对象在其他地方可能不太有用。
引用自:https://docs.python.org/zh-cn/3.7/c-api/cell.html
闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。
注意:只有嵌套在其他函数中的函数,才可能需要处理不在全局作用域中的外部变量。
原文:https://www.cnblogs.com/hqq2019-10/p/14414219.html