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

科技资讯 投稿 12300 0 评论

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

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

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

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

      赞同 (74) or 分享 (0)
      游客 发表我的评论   换个身份
      取消评论

      表情
      (0)个小伙伴在吐槽