Python闭包怎么实现代替类,如何访问定义闭包内的变量
Admin 2022-06-28 群英技术资讯 342 次浏览
有时我们会定义只有一个方法(除了__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中求一个数的平方,可以使用内置模块math,或者使用表达式num**2,还可以使用python的内置函数pow()。
这篇文章主要介绍了Python可视化神器pyecharts绘制雷达图,雷达图是以从同一点开始的轴上表示的三个或更多个定量变量的二维图表的形式显示多变量数据的图形方法
本文总结了两种方法来导入opencv与numpy包,第一种是直接在Pycharm中导入两个包,第二种是在官网下载相关文件进行配置,感兴趣的小伙伴们可以参考一下
TFRecord格式的文件存储形式会很合理的帮我们存储数据,本文主要介绍了Tensorflow中TFRecord生成与读取的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
这篇文章主要介绍Python中requests做接口测试的内容,下面有具体的过程和步骤,对新手学习和了解requests做接口测试的方法有一定借鉴价值,感兴趣的朋友可以参考下。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008