Python闭包技巧介绍
作者:Orion's 发布时间:2022-05-30 19:17:04
目录
1.闭包:用函数代替类
2.访问定义在闭包的内的变量
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__()
的实现)。不过,这仍然是一个非常有学术价值的例子,它告诉我们对闭包内部提供访问机制能够实现怎样的功能。
来源:https://www.cnblogs.com/orion-orion/p/15504127.html


猜你喜欢
- 引言Lambda 函数(也称为匿名函数)是函数式编程中的核心概念之一。支持多编程范例的 Python 也提供了一种简单的方法来定义 lamb
- 在学习 Python 类的时候,会碰见类中有 __init__() 这样一个函数,其实它就是 Python 的构造方法。构造方法类似于类似
- 表结构: mysql> desc demo; +-------+------------------+------+-----+---
- 用法示例:import random# 1)随机小数print(random.random()) # 获取大于0且小于1 之间的小数 &nb
- 我就废话不多说了,大家还是直接看代码吧~#coding=utf-8import cv2import numpy as npimg=cv2.i
- 温馨提示,只想看图的画直接跳到最后一节拉格朗日方程此前所做的一切三体和太阳系的动画,都是基于牛顿力学的,而且直接对微分进行差分化,从而精度非
- 误区 #12:TempDB的文件数和需要和CPU数目保持一致错误 哎,由于上述误区是微软“官方”的建议,
- 最终效果前言这是最近在学qt这个东西,然后又学会了调用api,然后就想了用pyqt5做一个GUI界面,后期也可以打包分享给其他人使用,所以就
- django处理Ajax跨域访问使用javascript进行ajax访问的时候,出现如下错误出错原因:javascript处于安全考虑,不允
- 在做数据分析或者统计的时候,经常需要进行数据正态性的检验,因为很多假设都是基于正态分布的基础之上的,例如:T检验。在Python中,主要有以
- 本文实例讲述了MySQL无法存储emoji表情解决方案。分享给大家供大家参考,具体如下:今天学习爬虫爬伯乐在线的文章,由于在文章中有emoj
- 一次性读取csv文件内所有行的数据<?php $file = fopen('windows_2011_s.csv',&
- 在许多网页中,当鼠标移到一张图片上时,又弹出另一张图片,做这种广告条,要用到Macromedia Dr
- 最近各地中小学都在开展线上教学,有些不自觉的小朋友们用电脑在线学习的时候会趁家长不在的时候偷偷玩游戏、看漫画。本程序screenshot.p
- 一、内容简介本篇文章着重结合源码版本V3.2.20介绍Reactive和Ref。前置技能需要了解Proxy对象的工作机制,以下贴出的源码均在
- 本文是对《Python Qt GUI快速编程》的第9章的堆叠窗口例子Vehicle Rental用Python3+PyQt5+Qt Desi
- 具体代码如下所示:# -*- coding: utf-8 -*-import redisimport datetime''&
- 初次安装完PyCharm后,新建项目时,遇到了No Python interpreter selected的问题。意思是说没有找到Pytho
- 一、简单介绍pip 是 Python 包管理工具,该工具提供了对Python 包的查找、下载、安装和卸载的功能,现在大家用到的所有包不是自带
- 下载和安装安装 WTForms 最简单的方式是使用 easy_install 和 pip:easy_install WTForms# orp