Python闭包技巧介绍

 更新时间:2021年12月03日 10:11:05   作者:Orion's Blog  
这篇文章主要介绍了Python闭包,所谓闭包就是用函数代替类,被外层函数包围的内层函数,它能够获取外层函数范围中的变量,感兴趣的小伙伴请和小编一起进入文章学习具体内容吧

1.闭包:用函数代替类

有时我们会定义只有一个方法(除了__init__()之外)的类,而这种类可以通过使用闭包(closure)来替代。闭包是被外层函数包围的内层函数,它能够获取外层函数范围中的变量(即使外层函数已执行完毕)。因此闭包可以保存额外的变量环境,用于在函数调用时使用。考虑下面这个例子,这个类允许用户通过某种模板方案来获取URL。

from urllib.request import urlopen
class UrlTemplate:
     def __init__(self, template) -> None:
         self.template = template
     def open(self, **kwargs):
         return urlopen(self.template.format_map(kwargs))
    
 yahoo = UrlTemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}')
 for line in yahoo.open(names='IBM,AAPL,FB', fields = 'sllclv'):
     print(line.decode('utf-8'))


这个类可以用一个简单的函数来替代:

def urltempalte(template):
    # 后面用对象再次调用函数的时候传入kwargs
    def opener(**kwargs):
        return urlopen(template.format_map(kwargs))
    return opener

yahoo = urltempalte('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}')
for line in yahoo(names='IBM,AAPL,FB', fields = 'sllclv'):
    print(line.decode('utf-8'))


在许多情况下,我们会使用只有单个方法的类的原因是需要保存额外的状态给类方法使用。我们上面提到的UrlTemplate类的唯一目的就是将template的值保存在某处,然后在open()方法中使用它。而使用闭包来解决该问题会更加简短和优雅,我们使用opener()函数记住参数template的值,然后再随后的调用中使用该值。
所以大家在编写代码中需要附加额外的状态给函数时,一定要考虑使用闭包。

2.访问定义在闭包的内的变量

我们知道闭包内层可以用来存储函数需要用到的变量环境。接下来,我们可以通过函数来扩展闭包,使得在闭包内层定义的变量可以被访问和修改。
一般来说,闭包内层定义的变量对外界来说是完全隔离的,如果想要访问和修改它们,需要编写存取函数(accessor function, 即getter/setter方法),并将它们做为函数属性附加到闭包上来提供对内层变量的访问支持:

def sample():
    n = 0
    # 闭包函数
    def func():
        print("n =", n)
    
    # 存取函数(accessor function),即getter/setter方法
    def get_n():
        return n

    def set_n(value):
        # 必须要加nolocal才能修改内层变量
        nonlocal n
        n = value

    # 做为函数属性附加
    func.get_n = get_n
    func.set_n = set_n
    return func

该算法测试运行结果如下:

f = sample()
f() # n = 0
f.set_n(10)
f() # n = 10
print(f.get_n()) # 10


可以看到,get_n()set_n()工作起来很像实例的方法。注意一定要将get_n()和set_n()做为函数属性附加上去,否则在调用set_n()get_n()就会报错:'function' object has no attribute 'set_n'
如果我们希望让闭包完全模拟成类实例,我们需要架构内层函数拷贝到一个实例的字典中然后将它返回。

示例如下:

import sys
class ClosureInstance:
    def __init__(self, locals=None) -> None:
        if locals is None:
            locals = sys._getframe(1).f_locals 
            
        # Update instance dictionary with callables
        self.__dict__.update(
            (key, value) for key, value in locals.items() if callable(value)
        )

    # Redirect special methods
    def __len__(self):
        return self.__dict__['__len__']()
    
# Example use
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) # <__main__.ClosureInstance object at 0x101efc280>
s.push(10)
s.push(20)
s.push('Hello')
print(len(s)) # 3
print(s.pop()) # Hello
print(s.pop()) # 20
print(s.pop()) # 10


用闭包模型类的功能比传统的类实现方法要快一些。比如我们用下面这个类做为测试对比。

class Stack2:
    def __init__(self) -> None:
        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'))
# 0.98746542
s = Stack2()
print(timeit('s.push(1);s.pop()', 'from __main__ import s'))
# 1.07070521


可以看到采用闭包的版本要快大约8%。因为对于实例而言,测试话费的大部分时间都在对实例变量的访问上,而闭包要更快一些,因为不用涉及额外的self变量。
不过这种奇技淫巧在代码中还是要谨慎使用,因为相比一个真正的类,这种方法其实是相当怪异的。像继承、属性、描述符或者类方法这样的特性在这种方法都是无法使用的。而且我们还需要一些花招才能让特殊方法正常工作(比如我们上面ClosureInstance中对__len__()的实现)。不过,这仍然是一个非常有学术价值的例子,它告诉我们对闭包内部提供访问机制能够实现怎样的功能。

到此这篇关于Python闭包技巧介绍的文章就介绍到这了,更多相关Python闭包内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • python工具dtreeviz决策树可视化和模型可解释性

    python工具dtreeviz决策树可视化和模型可解释性

    这篇文章主要介绍了python工具dtreeviz决策树可视化和模型可解释性,决策树是梯度提升机和随机森林的基本构建块,在学习这些模型的工作原理和模型可解释性时,可视化决策树是一个非常有帮助,下文相关资料,需要的小伙伴可任意参考一下
    2022-03-03
  • Python测试网络连通性示例【基于ping】

    Python测试网络连通性示例【基于ping】

    这篇文章主要介绍了Python测试网络连通性,结合实例形式分析了Python通过发送ping请求测试网络连通性相关操作技巧,需要的朋友可以参考下
    2018-08-08
  • TensorFlow实现checkpoint文件转换为pb文件

    TensorFlow实现checkpoint文件转换为pb文件

    今天小编就为大家分享一篇TensorFlow实现checkpoint文件转换为pb文件,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-02-02
  • Tensorflow 实现线性回归模型的示例代码

    Tensorflow 实现线性回归模型的示例代码

    这篇文章主要介绍了Tensorflow 实现线性回归模型,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • Python实现常见的4种坐标互相转换

    Python实现常见的4种坐标互相转换

    主流被使用的地理坐标系并不统一,常用的有WGS84、GCJ02(火星坐标系)、BD09(百度坐标系)以及百度地图中保存矢量信息的web墨卡托,本文利用Python编写相关类以实现4种坐标系统之间的互相转换,希望对大家有所帮助
    2023-02-02
  • python 针对在子文件夹中的md文档实现批量md转word

    python 针对在子文件夹中的md文档实现批量md转word

    这篇文章主要介绍了python 针对在子文件夹中的md文档实现批量md转word,但是自己保存的md文档在不同的文件夹,而大部分只能实现同一文件夹内的转换,得出下列总结,需要的朋友可以参考一下
    2022-04-04
  • numpy取反操作符和Boolean类型与0-1表示方式

    numpy取反操作符和Boolean类型与0-1表示方式

    这篇文章主要介绍了numpy取反操作符和Boolean类型与0-1表示方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • python链接Oracle数据库的方法

    python链接Oracle数据库的方法

    这篇文章主要介绍了python链接Oracle数据库的方法,实例分析了Python使用cx_Oracle模块操作Oracle数据库的相关技巧,需要的朋友可以参考下
    2015-06-06
  • Python如何使用matplotlib绘制柱状图

    Python如何使用matplotlib绘制柱状图

    这篇文章主要介绍了Python如何使用matplotlib绘制柱状图问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • 200行python代码实现2048游戏

    200行python代码实现2048游戏

    这篇文章主要为大家详细介绍了200行Python代码实现2048游戏,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-07-07

最新评论