Python闭包怎么实现代替类,如何访问定义闭包内的变量
Admin 2022-06-28 群英技术资讯 702 次浏览
有时我们会定义只有一个方法(除了__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
的值,然后再随后的调用中使用该值。
所以大家在编写代码中需要附加额外的状态给函数时,一定要考虑使用闭包。
我们知道闭包内层可以用来存储函数需要用到的变量环境。接下来,我们可以通过函数来扩展闭包,使得在闭包内层定义的变量可以被访问和修改。
一般来说,闭包内层定义的变量对外界来说是完全隔离的,如果想要访问和修改它们,需要编写存取函数(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__()
的实现)。不过,这仍然是一个非常有学术价值的例子,它告诉我们对闭包内部提供访问机制能够实现怎样的功能。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要介绍了python数据操作之lambda表达式详情,文章基于python的相关资料展开lambda表达式具体的内容,感兴趣的小伙伴可以参考一下
这篇文章主要为大家介绍了不修改数组找出重复的数字Python实现,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
今天我们来总结一下python装饰器的原理、作用和使用,之前我们以及有详细的了解过python装饰器了,因此这篇文章就带大家简单的回顾一下python装饰器的知识,有需要的朋友可以参考。
大家还记不记得曾经印在我们的铅笔盒的九九乘法表?我们从小就开始学习背诵九九乘法表,经常被家长和数学老师检查背诵。九九乘法表我们进行计算的前提,没了九九乘法表,我们就难以计算。小编之前向大家介绍过用while循环打印九九乘法表,那大家知不知道如何用for循环打印九九乘法表呢?今天,小编就向大家介绍如何打印九九乘法表。
这篇文章主要介绍了pytorch教程中网络的构建流程,文中附含了详细的示例代码流程,有需要的朋友可以借鉴参考下,希望可以有所帮助
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008