python高级:闭包

标签: python  闭包  函数闭包

由于闭包这个概念比较难以理解,尤其是初学者来说,相对难以掌握,所以我们通过示例去理解学习闭包。

给大家提个需求,然后用函数去实现:完成一个计算不断增加的系列值的平均值的需求。

例如:整个历史中的某个商品的平均收盘价。什么叫平局收盘价呢?就是从这个商品一出现开始,每天记录当天价格,然后计算他的平均值:平均值要考虑直至目前为止所有的价格。

比如大众推出了一款新车:小白轿车。

第一天价格为:100000元,平均收盘价:100000元

第二天价格为:110000元,平均收盘价:(100000 + 110000)/2 元

第三天价格为:120000元,平均收盘价:(100000 + 110000 + 120000)/3 元

........

 

 

series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)
    
print(make_averager(100000))
print(make_averager(110000))
print(make_averager(120000))

 

从上面的例子可以看出,基本上完成了我们的要求,但是这个代码相对来说是不安全的,因为你的这个series列表是一个全局变量,只要是全局作用域的任何地方,都可能对这个列表进行改变。

 

series = []
def make_averager(new_value):
    series.append(new_value)
    total = sum(series)
    return total / len(series)
    
print(make_averager(100000))
print(make_averager(110000))
series.append(666)  # 如果对数据进行相应改变,那么你的平均收盘价就会出现很大的问题。
print(make_averager(120000))

 

那么怎么办呢?有人说,你把他放在函数中不就行了,这样不就是局部变量了么?数据不就相对安全了么?

 

 

def make_averager(new_value):
    series = []
    series.append(new_value)
    total = sum(series)
    return total / len(series)
​
​
print(make_averager(100000))  # 100000.0
print(make_averager(110000))  # 110000.0
print(make_averager(120000))  # 120000.0

 

这样计算的结果是不正确的,那是因为执行函数,会开启一个临时的名称空间,随着函数的结束而消失,所以你每次执行函数的时候,都是重新创建这个列表,那么这怎么做呢?这种情况下,就需要用到我们讲的闭包了,我们用闭包的思想改一下这个代码。

 

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(100000))
print(avg(110000))
print(avg(120000))

 

大家仔细看一下这个代码,我是在函数中嵌套了一个函数。那么avg 这个变量接收的实际是averager函数名,也就是其对应的内存地址,我执行了三次avg 也就是执行了三次averager这个函数。那么此时你们有什么问题?

肯定有学生就会问,那么我的make_averager这个函数只是执行了一次,为什么series这个列表没有消失?反而还可以被调用三次呢?这个就是最关键的地方,也是闭包的精华所在。我给大家说一下这个原理,以图为证:

    上面被红色方框框起来的区域就是闭包,被蓝色圈起来的那个变量应该是make_averager()函数的局部变量,它应该是随着make_averager()函数的执行结束之后而消失。但是他没有,是因为此区域形成了闭包,series变量就变成了一个叫自由变量的东西,averager函数的作用域会延伸到包含自由变量series的绑定。也就是说,每次我调用avg对应的averager函数 时,都可以引用到这个自用变量series,这个就是闭包。

闭包的定义:

    1. 闭包是嵌套在函数中的函数。

    2. 闭包必须是内层函数对外层函数的变量(非全局变量)的引用。

如何判断判断闭包?举例让同学回答:

 

 

# 例一:
def wrapper():
    a = 1
    def inner():
        print(a)
    return inner
ret = wrapper()
​
# 例二:
a = 2
def wrapper():
    def inner():
        print(a)
    return inner
ret = wrapper()
​
​
# 例三:
​
def wrapper(a,b):
    def inner():
        print(a)
        print(b)
    return inner
a = 2
b = 3
ret = wrapper(a,b)

 

以上三个例子,最难判断的是第三个,其实第三个也是闭包,如果我们每次去研究代码判断其是不是闭包,有一些不科学,或者过于麻烦了,那么有一些函数的属性是可以获取到此函数是否拥有自由变量的,如果此函数拥有自由变量,那么就可以侧面证明其是否是闭包函数了(了解):

 

 

def make_averager():
​
    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
​
    return averager
avg = make_averager()
# 函数名.__code__.co_freevars 查看函数的自由变量
print(avg.__code__.co_freevars)  # ('series',)
当然还有一些参数,仅供了解:

# 函数名.__code__.co_freevars 查看函数的自由变量
print(avg.__code__.co_freevars)  # ('series',)
# 函数名.__code__.co_varnames 查看函数的局部变量
print(avg.__code__.co_varnames)  # ('new_value', 'total')
# 函数名.__closure__ 获取具体的自由变量对象,也就是cell对象。
# (<cell at 0x0000020070CB7618: int object at 0x000000005CA08090>,)
# cell_contents 自由变量具体的值
print(avg.__closure__[0].cell_contents)  # []

 

闭包的作用:保存局部信息不被销毁,保证数据的安全性。

闭包的应用

  1. 可以保存一些非全局变量但是不易被销毁、改变的数据。

  2. 装饰器。

版权声明:本文为SpringBears原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/SpringBears/article/details/105769508

智能推荐

hive实操二(连续7天登录的总人数)

求连续7天登录的总人数 数据: 所用SQL: 解析: 第一步:先使用窗口函数,根据用户id分组,日期排序 如下图: 第二步:在第一步的基础上,使用date_sub日期函数 ,date_sub(date,dateStr)表示,哪一天减多少天,使用这个函数的作用是计算出每个日期排名前的日期 如下图: 第三步:在第二步的基础上,根据用户id和日期分组,计算日期数目大于7的用户 如下图:...

20道25K+Android工程师面试必问面试题

25K大致算的上是Android开发的一个分水岭了。没点真正的东西,还真的拿不到25 本文讲解: 我们为什么要选择离职 面试必问面试题 如何选择心仪的公司 一.我们为何选择离职 工资跟不上消费 上班找不到归宿感和成绩感,上班感觉和坐牢一样 在公司没有发展空间 二.25K+Android工程师必问面试题 1.APK安装过程 应用安装涉及到如下几个目录: system/app:系统自带的应用程序,无法...

nginx实现反向代理

一.代理概念: 1.什么是正向代理和反向代理,概念我这里就不做解释,因为网上有大神已经解释的很好了,我这里给出链接https://www.cnblogs.com/Anker/p/6056540.html。 二.介绍nginx反向代理环境准备,工具准备: 1.首先你需要安装一个虚拟机VMware,虚拟机中安装一个linux系统,linux系统中需要安装两个tomcat服务器。 2.因为后面需要对反向...

第27课 二阶构造模式

本文内容取自于对狄泰学院 唐佐林老师 C++深度解析 课程的学习总结 构造函数的回顾 关于构造函数 类的 构造函数 用于对象的 初始化 构造函数 与类同名并且没有返回值 构造函数在对象定义时 自动被调用 问题 如判断 构造函数 的执行结果? 在构造函数中执行 return 语句会发生什么? 构造函数执行 结束是否意味着 对象构造成功? 为什么了回答这几个问题,我们编写一个构造函数,并且构造函数中添...

猜你喜欢

微机原理 第七章 8255A及I/O口

微机原理 第七章 8255A及I/O口 7.1 概述 一、 并行接口 什么叫并行接口? 连接CPU与并行外设的通道 以字节、双字节或字长为传输单位。 为什么要用?或者说,为什么会广泛应用? 传输速度快,但硬件开销大,近距离传输 一般传输什么信息? 传输的信息主要有状态信息、控制信息和数据信息,所以有对应的寄存器 在端口是不够用的时候,得进行扩展 二、可编程并行接口的功能 有什么特点? 具有端口寄存...

C语言非OS编程架构

    对于单片机非OS程序来说,好的架构必须具备如下特点:代码规范优雅,结构清晰,各模块之间低耦合。个人根据多年工作经历,总结如下:编写代码前应进行结构设计,C语言是面向过程的语言,所以一般系统结构分为三层:驱动层,功能模块层,任务调用层。为了降低耦合性,函数调用规则尽可能做到上层调用下层。     驱动层     非OS驱动层一般由硬...

spring boot 源码解析12-servlet容器的建立

前言 spring boot 一般都会加入如下依赖: 加入后,就会启动一个嵌入式容器,其默认启动的是tomcat.那么他是如何启动的,我们接下来就分析下. 解析 通过之前的文章我们知道了在SpringApplication#run方法的第9步会调用AbstractApplicationContext#refresh方法,而在该方法的第5步中会调用invokeBeanFactoryPostProce...

leetcode之除数博弈

原题链接 这道题我看到leetcode上有个巧妙解法,利用N的奇偶性就可以判断先手的输赢; 但是看到这道题目属于动态规划,那么就该朝着动态规划的思路去做,但是我觉得这道题虽然是简单题,但是有点绕。...

mkdir: Cannot create directory /usr/master/input. Name node is in safe mode.解决方案

一、问题描述 在Hadoop启动namenode后,创建目录时报错,错误如下: 显示namenode在安全模式下,无法创建文件夹。 二、问题解决 关闭namenode安全模式: 问题解决,重新创建可查看到创建的文件夹。          欢迎关注博主,欢迎互粉,一起学习!        感谢您的阅读,不足之处...