闭包:用函数代替类

有时我们会定义只有一个方法(除了__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. PHP中的MySQLi扩展学习(一)MySQLi介绍

    关于 PDO 的学习我们告一段落,从这篇文章开始,我们继续学习另外一个 MySQL 扩展,也就是除了 PDO 之外的最核心的 MySQLi 扩展.可以说它的祖先,也就是 MySQL(原始) 扩展是我们 ...

  2. PHP中的PDO操作学习(二)预处理语句及事务

    今天这篇文章,我们来简单的学习一下 PDO 中的预处理语句以及事务的使用,它们都是在 PDO 对象下的操作,而且并不复杂,简单的应用都能很容易地实现.只不过大部分情况下,大家都在使用框架,手写的机会非 ...

  3. 解决navicat 导出excel数字为科学计数法问题

    1.原因分析      用程序导出的csv文件,当字段中有比较长的数字字段存在时,在用excel软件查看csv文件时就会变成科学技术法的表现形式.     其实这个问题跟用什么语言导出csv文件没有关 ...

  4. MySQL之索引复合索引有效性

    首先这里建立一张数据表,并建立符合索引( index_A,index_B,index_C) CREATE TABLE `test_index_sequence` ( `Id` int(11) NOT ...

  5. python 建站教程

    主端:安装nginx uwsgi django pymysql mysql安装mysql到http://repo.mysql.com/里面找 mysql57-community-release-el7 ...

  6. P7515-[省选联考 2021A卷]矩阵游戏【差分约束】

    正题 题目链接:https://www.luogu.com.cn/problem/P7515 题目大意 有一个\(n*m\)的矩形\(A\),然后给出一个\((n-1)*(m-1)\)的矩形\(B\) ...

  7. Java多线程--实现同步的9种方法

    我们通常说的保持同步,其实就是对共享资源的保护.在单线程模型中, 我们永远不用担心"多个线程试图同时使用同一个资源的问题", 但是有了并发, 就有可能发生多个线程竞争同一个共享资源 ...

  8. 配置Internal Load balancer中VM的外网访问

    当在Azure中部署SQL VM时,处于安全考虑,不会配置VM的Public IP,会禁止外网的进出站访问,只允许从内部VNET,或者特定的内部IP访问.特别是当使用Azure Internal Lo ...

  9. 题解 P6454 麻将 加强版

    题目传送门 题目大意 不想写了,直接看题面吧. 思路 被这个题搞自闭了,因为读错题目想一个非常恶心的东西想了一场考试,然后就删代码,然后就被骂了.哎. 需要注意的是:我们只能选一次雀头 于是,不难看出 ...

  10. 【高热FAQ】关于智慧康养物联网加速器 ,你想知道的都在这

    摘要:从软硬件解决方案.设备接入到资源扶持,一文梳理智慧康养物联网加速器中ISV最关心的问题. 本文分享自华为云社区<[高热FAQ]关于智慧康养物联网加速器 ,你想知道的都在这>,作者:技 ...