返回函数

简单的说就是返回值是一个函数

返回的是函数,调用返回的函数的时候才会返回结果

def returnsum(a,b):
    def sum():
        return a+b
    return sum  # 这里将sum函数返回
test = returnsum(1,2)
print(test)
print(test())
<function returnsum.<locals>.sum at 0x0000000004EE6048>
3

每次调用都会返回一个新的函数,即使传入相同的参数

def returnsum(a,b):
    def sum():
        return a+b
    return sum 
test01 = returnsum(1,2)
test02 = returnsum(1,2)
print(test01 == test02)  # 每次调用都会返回新生成的函数
False

闭包(Closure)

相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”,比如上面例子的sum
函数闭包的特性,嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间

def closuretest(a):
    def myprint():
        print(a)
    return myprint
test = closuretest(1)
test()
1

上面的代码中,变量a是函数closuretest的一个本地变量,只应该存在于函数运行期间,但正因为闭包的特性,将a的值封装进了返回函数

def closuretest02(*args):
    f = []
    for i in args:
        def test():
            return i**2
        f.append(test)
    return f
test = closuretest02(1,2,3,4)
for i in test:
    print(i())
16
16
16
16

上面代码的结果是(16,16,16,16)而不是(1,4,9,16)
每次循环都会创建新的函数,加进列表中,但它并非立刻执行,返回的函数引用了变量i也不是在加进列表的时候就封装进函数了,
等到4个函数都返回时,它们所引用的变量i已经变成了4,所以打出的都是16
所以返回函数不要引用任何循环变量,或者后续会发生变化的变量

def closuretest03():
    def getnum(i):
        def test():
            return i**2
        return test
    f = []
    for i in range(1,5):
        f.append(getnum(i))
    return f
test = closuretest03()
for i in test:
    print(i())
1
4
9
16

上面的代码实现了(1,4,9,16)的输出,相当于多套了一层函数,append的函数是不一样的,在return test的时候,i已经被封装进去了,但代码就有些冗长了

def createCounter():
    f = [0]
    def counter():
        f[0] += 1
        return f[0]
    return counter
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
    print('测试通过!')
else:
    print('测试失败!')
1 2 3 4 5
测试通过!

上面的题很有意思啊,为什么要用f = [0]而不用f = 0呢。
首先counterA = createCounter(),这里counterA是return回来的counter,已经是一个返回回来的闭包了
如果用f = 0,再第二次再调用的时候f += 1,这里f就是未定义符号了
好吧,上面想错了,可以看到第一次就出错了,f = f + 1这里左边的f出错了,
UnboundLocalError: local variable ‘xxx’ referenced before assignment
在函数外部已经定义了变量n,在函数内部对该变量进行运算,运行时会遇到了这样的错误:
主要是因为没有让解释器清楚变量是全局变量还是局部变量。

def createCounter():
    f = 0
    def counter():
        f += 1
        return f
    return counter
counterA = createCounter()
print(counterA())
---------------------------------------------------------------------------

UnboundLocalError                         Traceback (most recent call last)

<ipython-input-64-bf086460db46> in <module>
      6     return counter
      7 counterA = createCounter()
----> 8 print(counterA())


<ipython-input-64-bf086460db46> in counter()
      2     f = 0
      3     def counter():
----> 4         f += 1
      5         return f
      6     return counter


UnboundLocalError: local variable 'f' referenced before assignment

这里改为ruturn f+1后不会报错,但同样无法实现递增

def createCounter():
    f = 0
    def counter():
        return f+1
    return counter
counterA = createCounter()
print(counterA(),counterA())
1 1

使用f = [0],之所以能行,就是f = [0]这个应用有自己的内存,有对象指着它所以不会回收
同理用global也是可以的

f = 0
def createCounter():
    #f = 0
    def counter():
        global f
        f += 1
        return f
    return counter
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
    print('测试通过!')
else:
    print('测试失败!')
1 2 3 4 5
测试失败!

但这样f的定义在函数外面去了,而且不同的返回闭包也公用了同一个全局变量,导致B的值不对
所以还可以使用nonlocal关键字

def createCounter():
    count = 0
    def counter():
        nonlocal count 
        count += 1
        return count
    return counter
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
    print('测试通过!')
else:
    print('测试失败!')
1 2 3 4 5
测试通过!

global与nonlocal

  • global适用于函数内部修改全局变量的值
  • nonlocal适用于嵌套函数中内部函数修改外部变量的值

    两者的功能不同。global关键字修饰变量后标识该变量是全局变量,对该变量进行修改就是修改全局变量,而nonlocal关键字修饰变量后标识该变量是上一级函数中的局部变量,如果上一级函数中不存在该局部变量,nonlocal位置会发生错误(最上层的函数使用nonlocal修饰变量必定会报错)。
    两者使用的范围不同。global关键字可以用在任何地方,包括最上层函数中和嵌套函数中,即使之前未定义该变量,global修饰后也可以直接使用,而nonlocal关键字只能用于嵌套函数中,并且外层函数中定义了相应的局部变量,否则会发生错误(见第一)。

def outside():
    a = 1
    print('outside ' + str(id(a)))
    def inside():
        nonlocal a
        print('inside  ' + str(id(a)))
    inside()
outside()
outside 8791273616208
inside  8791273616208

所以前面使用nonlocal后,返回了闭包中有父函数的变量,所以父函数那里不会被回收

def createCounter():
    def create_counter():
        c = 1
        while True:
            yield c
            c += 1
    cc = create_counter()
    def counter():
        return next(cc)
    return counter
counterA = createCounter()
print(counterA(),counterA())
1 2

上面利用生成器来实现也很有意思,核心就是返回的闭包中要包含变量(或者说可改变的量,比如这里的生成器,返回的就不是一个具体的值,而是一个可改变的生成器,按照我的理解,应为这里返回了生成器,就说明这个生成器对象还被指着,所以不会被回收清理掉)

参考

  1. 返回函数
  2. python面试系列之一
  3. Python3:返回函数、匿名函数lambda、装饰器、偏函数
  4. Python中关键字global与nonlocal的区别

路漫漫其修远兮,吾将上下而求索