python闭包——返回函数

函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:

def calc_sum(*args):
    ax = 0
    for n in args:
        ax = ax + n
    return ax

但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>

调用函数f时,才真正计算求和的结果:

>>> f()
25

在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False

f1()f2()的调用结果互不影响。

闭包

内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。

闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。

注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。

你可能认为调用f1()f2()f3()结果应该是149,但实际结果是:

>>> f1()
9
>>> f2()
9
>>> f3()
9

全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9

 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs

再看看结果:

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9

缺点是代码较长,可利用lambda函数缩短代码。

练习

利用闭包返回一个计数器函数,每次调用它返回递增整数:

方法1:使用list在函数内部引用其地址,并改变其值

def createCounter():
    a = [0]
    def counter():
        a[0] += 1
        return a[0]
    return counter

这里用 a = 0就不可行,如果一定要用a = 0需要借助nonlocal

#nonlocal很重要,不然会报变量作用域的错可以以重新定义变量

def createCounter():
    a=0
    def counter():
        nonlocal a
        a +=1
        return a
    return counter

这里解释起来就要设计到python中引用变量的顺序: 当前作用域局部变量->外层作用域变量->当前模块中的全局变量->python内置变量 。详见另一篇博客https://blog.youkuaiyun.com/qq_21294095/article/details/85094502

 方法2:使用generator

def createCounter():
    def g():
        n = 0
        while True:
            n = n+1
            yield n

    t = g()

    def counter():
        return next(t)

    return counter

小结

一个函数可以返回一个计算结果,也可以返回一个函数。

返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。

 

原地址:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431835236741e42daf5af6514f1a8917b8aaadff31bf000

 

### Python 闭包函数的定义 在 Python 中,闭包是一种特殊类型的函数结构。当一个嵌套函数在其作用域之外仍然能够记住并访问其外部函数的作用域中的变量时,这种机制就被称为闭包[^3]。具体来说,闭包由两部分组成:一个是函数本身,另一个是该函数所捕获的自由变量(即外部函数的局部变量)。因此,闭包的本质可以概括为“函数+引用环境”。 ```python def outer(): a = 5 # 外部函数的局部变量 def inner(): print(a) # 内部函数使用了外部函数的局部变量 return inner # 返回内部函数 func = outer() func() # 输出 5 ``` 上述代码展示了如何创建一个简单的闭包。`outer()` 是外部函数,在它的作用域中定义了一个名为 `a` 的局部变量以及一个嵌套函数 `inner()`。尽管 `outer()` 已经执行完毕,但由于闭包的存在,`inner()` 依然能访问 `a`。 --- ### Python 闭包的工作原理 闭包的核心在于延迟绑定和扩展作用域。通常情况下,局部变量会在函数退出后销毁;然而,如果某个局部变量被嵌套函数引用,则该变量会被保存下来以便后续使用。这一特性使得闭包能够在不同的上下文中保持状态信息[^2]。 以下是更详细的说明: 1. 当外部函数返回嵌套函数作为结果时,会形成一个闭包。 2. 嵌套函数保留对外部函数作用域中变量的引用,即使外部函数已经结束运行。 3. 这种设计模式允许程序逻辑更加模块化,并支持某些复杂的计算需求。 下面的例子进一步阐释了这一点: ```python def multiplier(n): def multiply(x): return n * x # 访问外部函数参数 n return multiply double = multiplier(2) triple = multiplier(3) print(double(5)) # 输出 10 print(triple(5)) # 输出 15 ``` 这里,`multiplier` 创建两个独立的闭包——分别对应于乘以二的操作 (`double`) 和乘以三的操作 (`triple`)。这两个闭包各自维护了自己的副本 `n` 来存储倍率值。 --- ### Python 闭包的常见使用场景 #### 场景一:封装数据 利用闭包可以在无需引入全局变量的情况下隐藏一些敏感的数据成员。例如: ```python def counter(): count = 0 def increment(): nonlocal count count += 1 return count return increment cnt = counter() print(cnt()) # 输出 1 print(cnt()) # 输出 2 ``` 此案例通过闭包实现了计数器的功能,其中 `count` 变量仅限于闭包内部可见,从而保护了数据的安全性[^3]。 #### 场景二:模拟类的行为 虽然 Python 支持完整的 OOP 特性,但在轻量化或者快速原型开发阶段,有时可以用闭包代替类来简化编码流程。比如前面提到过的计算器例子实际上也可以看作是对简单对象模型的一种模仿。 #### 场景三:装饰器的基础组件 许多高级特性的实现离不开闭包的支持,其中包括但不限于装饰器的设计。装饰器本质上就是一个接受目标函数作为输入并将增强后的版本输出的新函数。借助闭包技术可以让原生函数携带额外的状态或行为而不改变原有接口签名[^4]。 ```python from functools import wraps def log_decorator(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) print(f"{func.__name__} returned {result}") return result return wrapper @log_decorator def add(a, b): return a + b add(3, 4) # 输出 add returned 7 ``` 在这个样例里,我们构建了一款日志记录型装饰器,它基于闭包理念动态注入打印语句至任意指定的目标方法之中。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值