关于一道面试题的思考

def testFun():
    return [lambda x:i*x for i in range(4)]

for func in testFun():
    print(func(2))

  输出:

6
6
6
6

最开始百思不得其解。然后恰巧在知乎上看到这个例子。

这篇文章又引出了闭包的概念。下面这篇链接就是介绍闭包的。我从这篇链接得到了灵感。
后面会分析到,的确与闭包有关系。
 
第一个链接,即知乎上的部分解释我是有不同的理解。
实际上理解这段代码及其输出,我认为的关键点有两个。一个是理解闭包,一个是理解函数的定义阶段与函数的调用阶段这两个阶段分别干了什么。下面就一一分析。
 
part 1:
  testFun函数的返回值用到列表推导式以及匿名函数。所以,尽管只要一行代码,但实际上内容是很丰富的。
  将testFun代码转变为更容易理解的样式。如下:
def testFun1():
    l = []
    for i in range(4):
        def foo(x):
            return x*i
        l.append(foo)
    return l

  这里面需要注意以下几点:

    1 testFun 与 testFun1 完全等价。这一点一会证明。

    2 函数 foo 是一个闭包函数。这一点很重要。

    3 需要理解函数的定义阶段,与 函数的执行阶段到底做了什么。
  我们来验证第1点,实际上验证第1点同时也把第2点验证了。
    补充:如果一个函数是闭包,那么这个函数有__closure__属性。
  验证如下:
def testFun():
    return [lambda x:i*x for i in range(4)]

for func in testFun():
    print(func.__closure__,func(2))

print('='*80)
def testFun1():
    l = []
    for i in range(4):
        def foo(x):
            return x*i
        l.append(foo)
    return l

for func in testFun1():
    print(func.__closure__)

  输出:

(<cell at 0x0000025CC38C76D8: int object at 0x0000000075F3C6F0>,) 6
(<cell at 0x0000025CC38C76D8: int object at 0x0000000075F3C6F0>,) 6
(<cell at 0x0000025CC38C76D8: int object at 0x0000000075F3C6F0>,) 6
(<cell at 0x0000025CC38C76D8: int object at 0x0000000075F3C6F0>,) 6
================================================================================
(<cell at 0x0000025CC38C7BE8: int object at 0x0000000075F3C6F0>,)
(<cell at 0x0000025CC38C7BE8: int object at 0x0000000075F3C6F0>,)
(<cell at 0x0000025CC38C7BE8: int object at 0x0000000075F3C6F0>,)
(<cell at 0x0000025CC38C7BE8: int object at 0x0000000075F3C6F0>,)

  你会发现,testFun 与 testFun1 的返回值完全一样,同时返回的列表的每个元素都是闭包。每个闭包内的自由变量都是指向同一个自由变量。

  为什么呢?为什么是这样?我当初也是这样问自己。返回的列表内是闭包不难理解,但是为什么每个闭包的自由变量都是一样的呢?

  然后,我对上面代码加了验证。如下

def testFun1():
    l = []
    for i in range(4):
        print('flag1',i)
        def foo(x):
            print('flag2',i)
            return x*i
        l.append(foo)
    return l

for func in testFun1():
    print(func.__closure__)

  输出:

flag1 0
flag1 1
flag1 2
flag1 3
(<cell at 0x00000178EA0876D8: int object at 0x0000000075F3C6F0>,)
(<cell at 0x00000178EA0876D8: int object at 0x0000000075F3C6F0>,)
(<cell at 0x00000178EA0876D8: int object at 0x0000000075F3C6F0>,)
(<cell at 0x00000178EA0876D8: int object at 0x0000000075F3C6F0>,)

  看到这个输出结果,我感觉到恍然大悟,茅塞对开。

  testFun1 函数执行时,内部的 foo 函数,实际上只是处于定义阶段。函数在定义阶段值检测语法错误,不执行代码。

  如果你很难理解,看下面的代码就明白了。

def foo():
    print('from foo')
    bar()


def bar():
    print('from bar')

foo()

  看到这段代码,你可能觉得这段代码会报错。然而它会正常运行。输出结果如下:

from foo
from bar

  因为定义foo函数时,它并不会实际执行代码。如果执行,这段代码肯定会出错。所以,它只是会检测语法有没有错误而已。

  

  回归到上面。输出结果都是flag1,没有flag2,同样说明了testFun1函数内部定义的foo函数并没有执行,而仅仅是定义了而已。需要注意的是,这个foo是一个闭包函数,而闭包函数会对自由变量进行引用。

 

part 2:

  那么这段代码做了什么呢?

for func in testFun1():
    print(func(2))

  变量func是闭包函数,也就是函数 foo 。func(2) 这是对闭包函数的调用。实际上这才处于闭包函数的调用阶段。

  foo 内有一个对自由变量的引用。 当处于这里的调用阶段是,自由变量已经完成for循环。即 i 已经是 3 了。

  所以,这段代码内的 func,每一个func 都相当于下面wrapper函数的返回值,即闭包函数foo。

def wrapper():
i = 3
def foo(x):
return x * i
return foo

  所以,func(2)的返回值就是 6。面试题的答案自然就是 [6,6,6,6]了。

 

这就是我对这个面试题的理解。我觉的是没有问题的。如有不妥之处,还望指教。

  

 

  

posted @ 2018-05-12 00:37  骑者赶路  阅读(148)  评论(0编辑  收藏  举报