zoukankan      html  css  js  c++  java
  • Python中的返回函数与闭包

    返回函数,顾名思义,就是高阶函数可以把函数作为return值返回。与闭包的关系是:闭包需要以返回函数的形式实现。

    一. 返回函数

    比如我们有一个求和函数:

    >>> def calc_sum(num_list):
        s = 0
        for i in num_list:
            s += i
        return s
    
    >>> calc_sum([1,2,3,4])
    10

    当我们不需要立刻求和,而是后面根据需要再计算结果时,我们可以返回求和的函数,而不是直接返回计算结果。这就是返回函数。

    >>> def lazy_calc_sum(num_list):
        def calc_sum():
            s = 0
            for i in num_list:
                s += i
            return s
        return calc_sum
    
    >>> f_lazy = lazy_calc_sum([1,2,3,4])
    >>> f_lazy
    <function lazy_calc_sum.<locals>.calc_sum at 0x0000003A8D92E9D8>
    >>> f_lazy()
    10

    很显然,这样能让我们根据需求,节省计算资源。

    二. 闭包

    在上面的例子中,我们在函数lazy_clac_sum中又定义了函数calc_sum,并且,内部函数calc_sum可以引用外部函数lazy_calc_sum的参数和局部变量,当lazy_calc_sum返回函数calc_sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”。

    如果让定义更加清晰一些: 如果在一个内部函数里对在外部作用域(但不是在全局作用域)的变量进行引用,但不在全局作用域里,则这个内部函数就是一个闭包。

    实际上,闭包的用处/优点有两条:

    • 从函数外可以读取函数内部的变量
    • 让这些变量的值始终保持在内存中(也可以理解为保留当前运行环境)

     下面例子是,我们创建了一个下载download函数,然后下载次数一直存储在内存中。

    >>> def download_enter(download_times):
        def download():
    nonlocal download_times download_times
    += 1 print("This is the %s time download" % download_times) return download >>> >>> d = download_enter(0) >>> d() This is the 1 time download >>> d() This is the 2 time download >>> d() This is the 3 time download

    下面的例子是闭包根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。比如有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行。

    def make_filter(keep):  
        def the_filter(file_name):  
            file = open(file_name)  
            lines = file.readlines()  
            file.close()  
            filter_doc = [i for i in lines if keep in i]  
            return filter_doc  
        return the_filter  

    如果我们需要取得文件"result.txt"中含有"pass"关键字的行,则可以这样使用例子程序

    filter = make_filter("pass")  
    filter_result = filter("result.txt") 

    以上两种使用场景,用面向对象也是可以很简单的实现的,但是在用Python进行函数式编程时,闭包对数据的持久化以及按配置产生不同的功能,是很有帮助的。

    关于闭包的2个常见错误:

    1. 尝试在闭包中改变外部作用域的局部变量

    def foo():  
        a = 1  
        def bar():  
            a = a + 1  
            return a  
        return bar  
    >>> c = foo()  
    >>> print c()  
    Traceback (most recent call last):  
      File "<stdin>", line 1, in <module>  
      File "<stdin>", line 4, in bar  
    UnboundLocalError: local variable 'a' referenced before assignment  

    这段程序的本意是要通过在每次调用闭包函数时都对变量a进行递增的操作。但在实际使用时,a = a + 1的a会被python解释器认为是bar()函数的局部变量,从而引起“referenced before assignment”的错误。

    解决方法有两个:

      方法一:将a设置为一个容器,比如列表List (不推荐)

      方法二:将a声明为nonlocal变量(仅在Python3支持),这样声明过后,就不会被认为是bar()函数的局部变量,而是会到上一层函数环境中寻找这个变量。

    下面是例子:

    >>> def foo():
        a = 1
        b = [1]
        def bar():
            nonlocal a
            a = a + 1
            b[0] = b[0] + 1
            return a,b[0]
        return bar
    
    >>> c = foo()
    >>> print(c())
    (2, 2)
    >>> print(c())
    (3, 3)
    >>> print(c())
    (4, 4)

    2. 误以为返回的函数就已执行,对执行结果误判

    直接举例子说明:

    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
  • 相关阅读:
    tf.nn.in_top_k的用法
    随机初始化值randint,rand,tf.random_normal,tf.random_uniform
    归一化输入向量
    softmax函数理解
    梯度下降与反向传播
    深度学习-初始化权重矩阵
    学习--流畅的Python
    基础函数学习
    eclipse
    初学python
  • 原文地址:https://www.cnblogs.com/ArsenalfanInECNU/p/9628836.html
Copyright © 2011-2022 走看看