• Python 访问闭包中的变量



    一、问题

    扩展函数中的闭包,允许访问和修改函数的内部变量。



    二、解决方案

    通常,闭包的内部变量对外界是完全隐藏的。但可以编写访问函数,将其作为函数属性绑定到闭包上来实现访问。

    def sample():
        n = 0
        # 闭包函数
        def func():
            print('n=', n)
    
        # 属性n的访问方法
        def get_n():
            return n
    
        def set_n(value):
            nonlocal n
            n = value
    
        # 附加为函数属性
        func.get_n = get_n      # 将get_n函数附加为func函数的属性
        func.set_n = set_n
        return func
    
    f = sample()
    f()
    
    f.set_n(10)
    f()
    
    f.get_n()
    f()
    

    输出:

    n= 0
    n= 10
    n= 10
    

    注意两点:

    nonlocal 声明可以让我们编写函数来修改内部变量的值。

    函数属性允许将访问方法绑定到闭包函数上。



    三、讨论

    闭包模拟类的实例。仅仅是复制上面的内部函数到一个字典并返回。

    import sys
    
    class ClosureInstance:
        def __init__(self, locals=None):
            if locals is None:
                locals = sys._getframe(1).f_locals
    
            # 使用可调用对象更新字典
            self.__dict__.update((key,value) for key, value in locals.items()
                                if callable(value) )
        # 重定向的特殊方法
        def __len__(self):
            return self.__dict__['__len__']()
    
    def Stack():
        items = []
        def push(item):
            items.append(item)
    
        def pop():
            return items.pop()
    
        def __len__():
            return len(items)
    
        return ClosureInstance()
    
    s = Stack()
    print(s)
    
    s.push(10)
    s.push(20)
    s.push('hello')
    print(len(s))
    

    输出:

    <__main__.ClosureInstance object at 0x00000189D3A55F88>
    3
    

    这个代码比普通的类定义要快很多。

    class Stack2:
        def __init__(self):
            self.items = []
    
        def push(self, item):
            self.items.append(item)
    
        def pop(self):
            return self.items.pop()
    
        def __len__(self):
            return len(self.items)
    
    from timeit import timeit
    s = Stack()
    print(timeit('s.push(1);s.pop()', 'from __main__ import s'))
    s2 = Stack2()
    print(timeit('s2.push(1);s2.pop()', 'from __main__ import s2'))
    

    输出:

    0.3306974
    0.34851409999999994
    

    闭包方案运行起来要快 8%,原因是对实例变量的访问,闭包不会涉及到额外的 self 变量。



    注意:在代码的编写中一般不会这样做。原因如下:

    • 虽然它运行更快,但它只是真实类的一个奇怪替换。

    此时类的继承、属性、描述器、类方法都不能用。并且要做额外的工作才能让一些特殊方法生效(比如上面ClosureInstance 中重写的 __len__())。

    • 让阅读代码的人感到疑惑,看起来不是一个普通的类定义。


  • 相关阅读:
    重视个人成长 远离心灵鸡汤——由一则“心灵鸡汤”想到的
    vim 学习
    针对不同包之间的action跳转,怎么配置?
    Bootstrap 栅格系统
    struts2的s:iterator 标签 详解
    struts2 <s:property/>标签的使用--输出时间格式转换
    Myeclipse中把java代码导成UML类图
    大学毕业后坚持学习有什么用
    【BZOJ2754】喵星球上的点名(AC自动机)
    Codeforces Round #466 (Div. 2)
  • 原文地址:https://www.cnblogs.com/keye/p/15679359.html
Copyright © 2020-2023  润新知