python函数——闭包

概念介绍:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

闭包就是内层函数,对外层函数(非全局)的变量的引用,叫闭包 可以让一个局部变量常驻内存,防止其他程序修改这个变量

初始闭包

为了说明闭包中引用的变量的性质,可以看一下下面的这个例子:

python 复制代码
def f2(func):
    def f1():
        x = func()
        return x + 1
    return f1
def func():
    print("输出func()函数")
    return 1
dec = f2(func)
print(dec())
print(func())

输出结果为:

还可以将 f1( ) 函数的定义移动到 f2( )函数中,这样在 f2( ) 函数外的作用 域就不能直接调用 f1( ) 函数

闭包的概念:

如果内层函数引用了外层函数的变量(包括其参数),并且外层函数 返回内层函数名,这种函数架构称为闭包。

闭包满足的如下3个条件:

  1. 内层函数的定义嵌套在外层函数中。

  2. 内层函数引用外层函数的变量

  3. 外层函数返回内层函数名

闭包陷阱:

python 复制代码
def my_func(*args):
    fs = []
    for i in range(3):
        def func():
            return i * i
        fs.append(func)
    return fs

fs1, fs2, fs3 = my_func()
print(fs1())
print(fs2())
print(fs3())

上面这段代码可谓是典型的错误使用闭包的例子。程序的结果并不是我们想象的结果0,1,4。实际结果全部是4。

这个例子中,my_func返回的并不是一个闭包函数,而是一个包含三个闭包函数的一个list。这个例子中比较特殊的地方就是返回的所有闭包函数均引用父函数中定义的同一个自由变量。

但这里的问题是为什么for循环中的变量变化会影响到所有的闭包函数?尤其是我们上面刚刚介绍的例子中明明说明了同一闭包的不同实例中引用的自由变量互相没有影响的。而且这个观点也绝对的正确。

那么问题到底出在哪里?应该怎样正确的分析这个错误的根源。

其实问题的关键就在于在返回闭包列表list之前for循环的变量的值已经发生改变了,而且这个改变会影响到所有引用它的内部定义的函数。因为在函数my_func返回前其内部定义的函数并不是闭包函数,只是一个内部定义的函数。所以要避免这个情况。

可以通过下面这种方式进行0、1、4的输出,从而使得这个返回s的外部函数不冲突

python 复制代码
def my_func(*args):
    fs = []
    for i in range(3):
        def func(i = i):
            return i * i
        fs.append(func)
    return fs
fs1, fs2, fs3 = my_func()
print(fs1())
print(fs2())
print(fs3())

闭包的应用:

闭包(closure)是指在一个函数内部定义的函数,并且这个内部函数可以访问到其外部函数的局部变量。Python中闭包的应用场景很多,主要可以用于以下几个方面:

保持状态信息:闭包可以用来保持某些信息的状态,使得函数每次调用时可以记住上次调用的结果或状态。这在某些需要记忆或记录状态的场景中非常有用。

python 复制代码
def counter():
    count = 0
    def inner():
        nonlocal count
        count += 1
        return count
    return inner

c = counter()
print(c())  # 输出 1
print(c())  # 输出 2
print(c())  # 输出 3

在这个例子中,内部函数inner形成了闭包,可以访问并修改外部函数counter的局部变量count,从而实现了计数的功能。

封装私有变量:通过闭包,可以创建私有变量或函数,这些变量和函数对外部是不可见的,从而实现类似于面向对象编程中私有成员的效果。

python 复制代码
def private_counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    def decrement():
        nonlocal count
        count -= 1
        return count
    return increment, decrement

inc, dec = private_counter()
print(inc())  # 输出 1
print(inc())  # 输出 2
print(dec())  # 输出 1

在这个例子中,increment和decrement函数形成了闭包,可以访问并修改private_counter函数的局部变量count,但这个count对外部是不可见的。

实现装饰器:装饰器本质上就是闭包,它可以在不修改原函数代码的情况下,给函数添加额外的功能

python 复制代码
def logger(func):
    def wrapper(*args, **kwargs):
        print(f'Calling function {func.__name__} with args {args} and kwargs {kwargs}')
        return func(*args, **kwargs)
    return wrapper

@logger
def add(a, b):
    return a + b

print(add(3, 5))  # 输出 Calling function add with args (3, 5) and kwargs {},以及 8

这里的logger函数是一个闭包,它接受一个函数作为参数,返回一个新的函数wrapper,实现了在调用add函数时打印日志的功能。

相关推荐
冻柠檬飞冰走茶2 小时前
字母移动游戏题解:贪心求最长可保留连续子串(附 C 实现)
c语言·开发语言·数据结构·算法·动态规划
FRing033 小时前
vue2干货总结7(自定义指令、插槽、单页应用程序、路由的使用(VueRouter))
开发语言·前端·vue.js·vue2
橙橙笔记3 小时前
具身智能的学习 - 完成一个机械臂 第1节
python·学习·机械臂·具身智能
雪落漂泊3 小时前
C++算法 二分查找篇(上)
开发语言·c++·算法
小小张说故事3 小时前
Python 的 class 语句到底执行了什么?拆开 __prepare__、metaclass.__new__ 和那个被讲反的 __set_name__ 顺序
python
波力海苔夹心脆6754 小时前
C# WPF 资源字典详解:ResourceDictionary、MergedDictionaries 与自定义控件案例
开发语言·c#·wpf·入门教程·resource·资源字典
不会算法的笨小孩4 小时前
异常的使用以及会出现的问题
开发语言·c++·c++11·异常
天天被压力5 小时前
【A股分钟级高频数据完全手册 #07】北交所历史分钟K线接口:京市Pro版分时批量
java·人工智能·python
天天被压力5 小时前
【A股分钟级高频数据完全手册 #08】股票日、分时数据接口对比:普通分时与Pro版差异实测
java·人工智能·python
特立独行的猫A5 小时前
AI 时代,我为什么推荐用 cj-tauri 开发桌面应用
java·后端·python