闭包:用函数代替类

有时我们会定义只有一个方法(除了__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__()的实现)。不过,这仍然是一个非常有学术价值的例子,它告诉我们对闭包内部提供访问机制能够实现怎样的功能。

参考文献

  • [1] https://www.python.org/
  • [2] Martelli A, Ravenscroft A, Ascher D. Python cookbook[M]. " O'Reilly Media, Inc.", 2005.

Python技法4:闭包的更多相关文章

  1. 说说Python中的闭包 - Closure

    转载自https://segmentfault.com/a/1190000007321972 Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西 ...

  2. 说说Python中的闭包

    Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西. 闭包的概念 我们尝试从概念上去理解一下闭包. 在一些语言中,在函数中可以(嵌套)定义另一个 ...

  3. Python中的闭包 - Closure

    Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西. 闭包的概念 我们尝试从概念上去理解一下闭包. 在一些语言中,在函数中可以(嵌套)定义另一个 ...

  4. 21.python中的闭包和装饰器

    python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure). 以下说明主要针对 python ...

  5. 【转】python中的闭包

    转自:http://www.cnblogs.com/ma6174/archive/2013/04/15/3022548.html python中的闭包 什么是闭包? 简单说,闭包就是根据不同的配置信息 ...

  6. Python 入门之 闭包

    Python 入门之 闭包 1.闭包 (1)在嵌套函数内使用(非本层变量)和非全局变量就是闭包 (2)_ closure _ 判断是不是闭包 def func(): a = 1 def foo(): ...

  7. Python技法:实现简单的递归下降Parser

    1. 算术运算表达式求值 在上一篇博文<Python技法:用re模块实现简易tokenizer>中,我们介绍了用正则表达式来匹配对应的模式,以实现简单的分词器.然而,正则表达式不是万能的, ...

  8. Python核心编程-闭包

    百度搜了一下闭包的概念:简而言之,闭包的作用就是在外部函数执行完并返回后,闭包使得收机制不会收回函数所占用的资源,因为内部函数的执行需要依赖外函数中的变量.这是对闭包作用的非常直白的描述,不专业也不严 ...

  9. Python深入04 闭包

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 闭包(closure)是函数式编程的重要的语法结构.函数式编程是一种编程范式 (而 ...

随机推荐

  1. Apache Dolphin Scheduler - Docker Compose 详解

    Apache DolphinScheduler 是一个分布式去中心化,易扩展的可视化 DAG 工作流任务调度系统.简称 DS,包括 Web 及若干服务,它依赖 PostgreSQL 和 Zookeep ...

  2. PHP设计模式之门面模式

    门面模式,也叫外观模式.不管是门面还是外观,都是我们对外的媒介,就好像我们的脸面一样.所以,这个模式最大的特点就是要表现的"好看".怎么说呢?一堆复杂的对象调用,自己都看蒙了,特别 ...

  3. 数组转为unicode字符编码字符串

    json_encode($data, JSON_UNESCAPED_UNICODE)在创建微信卡券,发送数据时需要这个

  4. nginx 常用x代码

    1.nginx 禁止ip直接访问,只允许域名访问,直接在.conf文件里 server上面再添加一个server 代码,不可以写同一个server里: server { listen 80 defau ...

  5. 『Python』matplotlib实现GUI效果

    1. 类RadioButtons的使用方法 类似单选框 import numpy as np import matplotlib.pyplot as plt import matplotlib as ...

  6. 定要过python二级选择题二套

    1. 2. 3. 4. 5. 6. 7. 8. 非主属性???? 9. 10.. 11.` 调用只是调用,但是没有返回值,,应为里面没有输出 12. 13. 14. 15. 16. 18. 19.

  7. Anaconda和canda简介及区别

    Anaconda简介: 1.是一个开源的Python发行版本,其包含了conda.Python等软件包,numpy,pandas(数据分析),scipy等科学计算包,而无需再单独下载配置. 可以在同一 ...

  8. [洛谷日报#204] StackEdit——Markdown 编辑器的功能介绍

    本文同时发表于洛谷日报,您也可以通过洛谷博客进行查看. 1.介绍与开始使用 1.1 这是什么? StackEdit是基于PageDown.Stack Overflow和其他堆栈交换站点使用的Markd ...

  9. NLP与深度学习(六)BERT模型的使用

    1. 预训练的BERT模型 从头开始训练一个BERT模型是一个成本非常高的工作,所以现在一般是直接去下载已经预训练好的BERT模型.结合迁移学习,实现所要完成的NLP任务.谷歌在github上已经开放 ...

  10. ldirectord

    试想,LVS作为前端负载均衡设备,当后端服务器宕机时,LVS还会把用户请求发送到该服务器上,这对用户体验来说是极其糟糕的,因为用户的请求无法得到处理.那么是否有一种机制,能保证后端服务器的是否正常?或 ...