• 轻松理解python中的闭包和装饰器(上)


         继面向对象编程之后函数式编程逐渐火起来了,在python中也同样支持函数式编程,我们平时使用的map, reduce, filter等都是函数式编程的例子。在函数式编程中,函数也作为一个变量存在,对应的函数名就是这个变量的名字,我们可以重新对其赋值,例如abs=len, abs( [1,2,3] ) 返回的是数字3。既然函数是个变量那我们就可以将函数做为参数,和返回值进行传递。

    首先我们讲解函数做为返回值:

    1 def outFunc():
    2     print 'call outFunc()...'
    3     
    4     def inFunc():
    5         print 'call inFunc()...'
    6     
    7     return inFunc   #返回定义好的函数

     在上面的函数定义中,我们在函数 outFunc 内部又定义了一个函数 inFunc。准确来说是定义了一个函数对象,用 inFunc 做为它的名字。最后外层函数 outFunc 返回变量 inFunc,也就是这个函数对象。我们可以通过调用outFunc获得这个对象:

    1 >>> x = outFunc()   # 调用outFunc()
    2 call outFunc()...
    3 >>> x               # 变量x是outFunc()返回的函数:
    4 <function inFunc at 0x1037bf320>
    5 >>> x()             # x指向函数,因此可以调用
    6 call inFunc()...    # 调用x()就是执行inFunc()函数定义的代码

    请注意区分返回函数和返回值:

    1 def myabs():
    2     return abs        # 返回函数
    3 def myabs2(x):
    4     return abs(x)   # 返回函数调用的结果,返回值是一个数值

    通过返回函数我们可以把一些计算延迟执行。例如,如果定义一个普通的求和函数:

    1 def calc_sum(lst):
    2     return sum(lst)

    调用calc_sum()函数时,将立刻计算并得到结果:

    1 >>> calc_sum([1, 2, 3, 4])
    2 10

    但是,如果返回一个函数,就可以“延迟计算”:

    1 def calc_sum(lst):
    2     def lazy_sum():
    3 return sum(lst) 4 return lazy_sum
    1 >>> f = calc_sum([1, 2, 3, 4])  # 调用calc_sum()并没有计算出结果,而是返回函数:
    2 >>> f
    3 <function lazy_sum at 0x1037bfaa0>
    4 >>> f()                        # 对返回的函数进行调用时,才计算出结果:
    5 10

    由于可以返回函数,我们在后续代码里就可以决定到底要不要调用该函数。 此外还有一个优点就是内部定义的函数无法被外部访问,例如lazy_sum函数无法被直接调用,只能通过calc_sum进行调用。

    同时注意我们在lazy_sum中引用了 calc_sum 的参数 lst, 用 f 接收返回的函数,再调用 f 时我们并没有传递参数,但它仍然可以继续工作,说明 lst 已经被绑定在lazy_sum内部了,成为函数的一部分,我们调用函数自然就可以操作 lst 求得 lst的和。

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

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

     1 # 希望一次返回3个函数,分别计算1x1,2x2,3x3:
     2 def count():
     3     fs = []
     4     for i in range(1, 4):
     5         def f():
     6              return i*i
     7         fs.append(f)
     8     return fs
     9 
    10 f1, f2, f3 = count()   #进行调用

    你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果全部都是 9(请自己动手验证)。

    原因就是当count()函数返回了3个函数时,这3个函数所引用的变量 i 的值已经变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时:

    1 >>> f1()
    2 9     # 因为f1现在才计算i*i,但现在i的值已经变为3

    因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量。

    考察下面的函数 f:

    1 def f(j):
    2     def g():
    3         return j*j
    4 
    5     return g

    它可以正确地返回一个闭包g,g所引用的变量j不是循环变量,因此将正常执行。

    在count函数的循环内部,如果借助f函数,就可以避免引用循环变量 i 。

     1 def count():
     2     fs = []
     3 
     4     for i in range(1, 4):
     5         def f(j):           
     6             def g():
     7                 return j*j   # g所引用的变量j不是循环变量,而是上层函数传递进来的参数
     8             return g
     9 
    10         r = f(i)
    11         fs.append(r)
    12     return fs
    13 
    14 f1, f2, f3 = count()         #进行调用
    15 print f1(), f2(), f3()     

     讲完了函数做为返回值,下一节我们讲解函数做为参数和装饰器的相关概念,精彩继续

  • 相关阅读:
    Python导学基础(三)输入、格式化输出、基本运算符
    题解-FJOI2014 树的重心
    题解-CF1307G Cow and Exercise
    题解-SHOI2005 树的双中心
    笔记-CF643E Bear and Destroying Subtrees
    题解-CF643G Choosing Ads
    扩展Lucas
    线性筛筛积性函数
    整除分块(数论)
    2019暑假集训DAY17(problem2.b)(杜教筛)
  • 原文地址:https://www.cnblogs.com/gaorong/p/6436374.html
Copyright © 2020-2023  润新知