首页 > 编程语言 >Python常见面试题016. 请实现如下功能|谈谈你对闭包的理解

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

时间:2023-04-10 17:02:46浏览次数:47  
标签:闭包 __ 面试题 co 函数 Python series avg averager

016. 请实现如下功能|谈谈你对闭包的理解

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

  • 实现一个函数(可以不是函数)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的实例

  • avg有个属性series,一开始是个空列表

  • __call__使得avg对象可以像函数一样调用

  • 调用的时候series会保留,因为series只在第一次初始化的时候置为空列表

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


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

闭包实现

  • 参考代码

    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 初识

  • 闭包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)。这是一个技术术语,指未在本地作用域中绑定的变量

  • image-20230410161657612

  • averager 的闭包延伸到那个函数的作用域之外,包含自由变量 series 的绑定

反汇编(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] 保存着真正的值
    
    
    
  • 闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。

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

标签:闭包,__,面试题,co,函数,Python,series,avg,averager
From: https://www.cnblogs.com/wuxianfeng023/p/17303474.html

相关文章

  • python抓取星巴克图片案例
    importrequestsfrombs4importBeautifulSoupr=requests.get('https://www.starbucks.com.cn')soup=BeautifulSoup(r.text,'lxml')imgs=soup.select('.padded-2>aimg[src]')num=0forimginimgs:num+=1#......
  • 面试题
    面试题1什么是gil锁gil锁:全局解释器锁,他的本质是一个大的互斥锁,他是cpython的一种机制,gil只存在cpython解释器,他限制了一个线程只有获取到gil锁才能执行,如果没有拿到gil锁,线程是不能执行的解释器有:cpython,pypython,jpythongil锁的作用是什么? 限制线程只有获取......
  • 用 Go 剑指 offer:面试题61. 扑克牌中的顺子
    从若干副扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为0,可以看成任意数字。A不能视为14。 示例 1:输入:[1,2,3,4,5]输出:True 示例 2:输入:[0,0,1,2,5]输出:True 限制:数组长度为5 数组的......
  • Python中如何将列表数据清空?
    Python列表可以用来存放相同数据类型的元素,既然能添加元素,那么也能删除元素。但是删除时,因为列表数据较多,一个一个删除太过于麻烦,进度还慢,这时有小伙伴就会问我:Python中如何将列表数据清空?本文为大家介绍两种方法,一起来看看吧。1、使用del关键字del可以用来清除范围中......
  • Python Http 请求
    如果要进行客户端和服务器端之间的消息传递,我们可以使用HTTP协议请求HTTP协议请求主要分6种类型(GET和POST较常用)1)GET请求通过URL网址传递信息,可以直接在URL中写上要传递的信息,也可以由表单进行传递(表单中的信息会自动转化为URL地址中的数据,通过URL地址传递)备注:已经取得资源,并......
  • 【Python】pip intall 遇到了WARNING: Retrying (Retry(total=4, connect=None, read=
    具体可能会遇到的是以下这些情况:WARNING:Retrying(Retry(total=4,connect=None,read=None,redirect=None,status=None))afterconnectionbrokenby'ProxyError('Cannotconnecttoproxy.',OSError(0,'Error'))'requests.exceptions.ProxyErro......
  • python操作mysql数据库
    Python操作mysql库python操作mysql数据库,需要使用第三库:pymysql一、mysql安装官网:https://www.mysql.com/二、安装pymysqlpipinstallPyMySql-ihttp://pypi.douban.com/simple/--trusted-hostpypi.douban.com三、使用代码演示:importpymysqlconn=pymysql.connec......
  • 软件测试|超好用超简单的Python GUI库——tkinter(四)
    前言之前我们介绍了label控件,本篇文章我们将介绍button控件。Button控件是Tkinter中常用的窗口部件之一,同时也是实现程序与用户交互的主要控件。通过用户点击按钮的行为来执行回调函数,是Button控件的主要功用。首先自定义一个函数或者方法,然后将函数与按钮关联起来,最后,当用户......
  • 软件测试|超好用超简单的Python GUI库——tkinter(五)
    前言在之前,我们介绍了tkinter的button控件,label控件,今天我们介绍一下entry控件,entry控件我们可以理解为界面的内容输入框,实现GUI界面与用户的信息交互,最典型的场景就是我们在登录时需要输入的账号密码。Entry控件使用起来非常简单,下面对该控件做简单的介绍。基本语法格式如下:tk_en......
  • python找图
    importcv2fromPILimportImageGrabimportnumpyasnpimportpyautoguiif__name__=='__main__':im=ImageGrab.grab()im.save('./res/screen.png','png')img_rgb=cv2.imread('./res/screen.png')......