首页 > 编程语言 >Python闭包和保存自由变量

Python闭包和保存自由变量

时间:2024-10-11 16:44:05浏览次数:8  
标签:闭包 __ return 变量 Python self def 函数

1.闭包:用函数代替类

术语闭包(closure) 来自抽象代数。抽象代数里,一集元素称为在某个运算(操作)之下封闭,如果将该运算应用于这一集合中的元素,产出的仍然是该集合中的元素。然而在Python社区中还用术语“闭包”表述于此一个毫不相干的概念。注意我们文章中所称的闭包为Python中的闭包,而非抽象代数中的闭包。

Python中的闭包是一种特殊的被外层函数包围的内层函数,它能够在外层函数执行完毕后仍然能获取在外层函数范围中、但未绑定到内层函数作用域的自由变量(free variable))。因此闭包可以保存额外的变量环境,用于在函数调用时使用。比如下列代码:

def make_printer(msg):
    def printer():
        print(msg)
    return printer

printer = make_printer('Foo!')
printer() #Foo!

在对内层函数printer()进行调用时,外层函数make_printer()已经执行完毕,但printer()仍可获取自由变量msg。

注意,闭包必须要要有对自由变量获取,如果没有获取自由变量,则只是一个普通的嵌套函数,如下图所示:

def make_printer(msg):
    def printer():
         pass
    return printer
printer = make_printer('Foo!')

那么闭包有什么使用价值呢?

有时我们会定义只有一个方法(除了__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.访问已保存的自由变量

我们知道闭包函数可以获取到被额外保留的变量环境。但这些变量对闭包函数来说是自由变量,还未绑定到内层函数的作用域。如果我们尝试修改这些变量:

def sample():
    n = 0
    # 闭包函数
    def func():
        n += 1
        print("n =", n)
    return func

f = sample()
f()

就会抛出UnboundLocalError:local variable 'n' referenced before assignment。

此时我们就需要nonlocal关键字将自由变量与内层函数绑定,然后就可以对其进行修改了:

def sample():
    n = 0
    # 闭包函数
    def func():
        nonlocal n
        n += 1
        print("n =", n)
    return func

f = sample()
f() # n = 1
f() # n = 2
f2 = sample() # # another instance, with a different stack
f2() # n = 1

更进一步地,我们会通过函数来扩展闭包,使得保存的自由变量可以在外界被访问和修改。一般来说,保存的自由变量对外界来说是完全隔离的,如果想要访问和修改它们,需要编写存取函数(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

用闭包模型类的功能比传统的类实现方法要快一些。比如我们用下面这个类做为测试对比。

#学习中遇到问题没人解答?小编创建了一个Python学习交流群:531509025

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

标签:闭包,__,return,变量,Python,self,def,函数
From: https://www.cnblogs.com/xxpythonxx/p/18458822

相关文章

  • Python 枚举 Enum
    Python中的枚举数据类型(Enum)_pythonenum-CSDN博客默认属性name和valuefromenumimportEnumclassWeekday(Enum):monday=1tuesday=2wednesday=3thirsday=4friday=5saturday=6sunday=7print(Weekday.wednesday)......
  • verilog文件到tb的简单python脚本
    使用python3verilog_to_tb.pytest.v需要注意的是,例化时并没有去掉最后的逗号,手动去除下。verilog_to_tb.pyimportsysimportredefread_file(file):withopen(file,'r')asf:lines=f.readlines()returnlinesdefmain():lines=read_file('......
  • Python编程:创意爱心表白代码集
    在寻找一种特别的方式来表达你的爱意吗?使用Python编程,你可以创造出独一无二的爱心图案,为你的表白增添一份特别的浪漫。这里为你精选了六种不同风格的爱心表白代码,让你的创意和情感通过代码展现出来。话不多说,咱直接上代码!1.紫色浪漫:心形表白#1-1导入turtle模块进行设计......
  • 配置JDK环境变量
    1.打开JDK的包,复制路径(比如我的就是D:\jdk-11)如果此时文件夹中没有jre,可以自动生成:直接点击当前路径输入cmd,点击回车,然后输入以下代码点击回车即可bin\jlink.exe--module-pathjmods--add-modulesjava.desktop--outputjre2.右键此电脑->属性->高级系统设置->环境......
  • Rust 变量和基础类型
    Rust变量和基础数据类型2024-03-13类型,元组,数组,切片,动态数组,生命周期,动态大小,静态大小Rust通过let关键字声明变量,变量默认是不可变(只读)的.letfoo=5;//创建了一个不可变的变量foo,并绑定了5foo=10;//不能对foo重新绑定//使用mut关键字创......
  • Python 装饰器
    Python装饰器1.装饰器是什么装饰器是Python中的一种高级函数,用于在不修改原始函数代码的前提下,动态地为函数或类增加功能。它本质上是一个函数,接受另一个函数或类作为参数,并返回一个新的函数或类。装饰器的基本语法defdecorator(func):defwrapper(*args,**kwargs)......
  • 教你如何免费获取股票数据用python、JavaScript (Node.js)、JAVA等多种语言的实例代码
    ​近一两年来,股票量化分析逐渐受到广泛关注。而作为这一领域的初学者,首先需要面对的挑战就是如何获取全面且准确的股票数据。因为无论是实时交易数据、历史交易记录、财务数据还是基本面信息,这些数据都是我们进行量化分析时不可或缺的宝贵资源。我们的核心任务是从这些数据......
  • centos7 安装Python3及配置环境变量
    centos7安装Python3并配置环境变量亲测有效python官网(linux下载地址、版本可自选):https://www.python.org/ftp/python/一、添加阿里云第三方扩展源仓库(安装拓展源仓库才能安装yum安装openssl11openssl11-devel,python源码编译必须要openssl11或以上版本)备注,我这里是把pyhto......
  • 雷达图怎么绘制?!超简单,一次性告诉你Python和R绘制方法~~
    今天给大家介绍的的图表为雷达图(Radar/Spiderchart),这种类型图表在生活中较常使用,是一种以从同一点开始的轴上表示的三个或更多个定量变量的二维图表的形式显示多变量数据的图形方法。较常用的场景多为分析企业经营状况(收益性、生产性、流动性、安全性和成长性)。本期推文......
  • python 09-pycharm连接远程服务器
    准备一个云服务器(linux操作系统)找一个免费试用的服务器用于练习,这里用这个服务器:在pycharm中连接服务器‌-VirtualEnvironment‌:‌Python的虚拟环境,‌允许每个项目拥有独立的库和解释器,‌避免依赖冲突。‌通过virtualenv等工具创建,‌适用于隔离项目依赖。‌‌-Sy......