资讯专栏INFORMATION COLUMN

python面试题之“该死的for循环系列”(二)

shiweifu / 395人阅读

摘要:到这里,如果你明白了,我们就可以继续进行下一步了理解匿名函数。

似乎只要一沾上for循环,难度立刻加倍,下面我们来看一道python的面试题:

要求写出下面代码的输出结果并且解释原因。

def multipliers():
    return [lambda x:i*x for i in range(4)]
print([m(2) for m in multipliers()])

这道题涉及的知识点包括以下几个方面:

1、列表推导式
2、匿名函数
3、闭包函数
4、for循环对函数的迭代调用
5、闭包函数的调用

首先我们来说一下列表推导式,只有深入理解列表推导式,我们才能理解下面这句话到底干了些什么事情[lambda x:i*x for i in range(4)]
引用官方文档中对于列表推导式的一个例子:squares = [x2 for x in range(10)] 这个列表推导式返回的结果为[0, 1, 4, 9, 16, 25, 36, 49, 64, 81],for循环通过对range(10)进行迭代后得到每个x的值,然后对它进执行x2的操作,最终结果为一个列表
那么如果不用列表推导式如何达到这个目的呢?答案如下,这个列表推导式等同于下面的代码:

squares = []
for x in range(10):
    squares.append(x**2)

这段代码执行后,squares的结果一样是[0, 1, 4, 9, 16, 25, 36, 49, 64, 81],根据这个例子我们可以简单地认为列表推导式是这样工作的:首先它会定义一个空列表,然后根据设定的条件得到一个一个的元素,同时把元素添加进列表中。

现在回到我们这道题,来看一下本题中的[lambda x:i*x for i in range(4)]这个列表推导式,如果把它拆开来的话它等价于下面的这段代码:

squares = []
for i in range(4):
    res = lambda x:i*x
    squares.append(res)

最终squares就是列表推导式的结果(一个列表),然后我们再研究下这个列表中的元素都是什么。
到这里,如果你明白了,我们就可以继续进行下一步了——理解匿名函数。

匿名函数的关键字为lambda,表现形式为:lambda 参数 : 返回值,lambda后面的参数就是函数的形参,冒号后面的表达式就是返回值。
比如:lambda a, b: a+b 这个简单的匿名函数可以传入两个参数a和b,结果返回a+b,这里要记住,只有调用这个匿名函数,它才会执行冒号后面的代码,这也是函数的执行法则,只有被调用时,函数内部的命名空间才会生效,在被调用之前它就是一个函数名指向的内存地址而已。

匿名函数虽然是匿名的,但是它也可以有名字,也可以作为一个结果赋值给任意的变量,所以它显然可以成为一个函数的返回值,也可以变成一个列表的元素,只不过此时这个列表的元素是匿名函数对应的内存地址罢了。见下面的例子:

#匿名函数直接赋值给变量lam
lam = lambda a,b:a+b
#此时lam指向了匿名函数的内存地址
print(lam)#此时的lam就是一个内存地址: at 0x7fecdc6b7e18>
res = lam(2,5) #调用匿名函数,把结果赋值给res
print(res)
 at 0x7fecdc6b7e18>
7

接下来我们说一下闭包,当前函数引用到上一层函数的局部命名空间的变量时就会触发闭包规则。我们说触发了闭包的函数叫做闭包函数,但是要注意一点:只有当调用闭包函数的时候它才会去引用外层函数的变量,因为在调用闭包函数之前,闭包内部的命名空间还不存在。

然后我们回头看这道题的代码:

def multipliers():
    return [lambda x:i*x for i in range(4)]
print([m(2) for m in multipliers()])
#根据前面的叙述,我们可以把它改成容易理解的形式:
def multipliers():
    squares = []
    for i in range(4):
        res = lambda x:i*x
        squares.append(res)
    return squares
print([m(2) for m in multipliers()])

匿名函数lambda x:i*x引用了外层函数multipliers()的命名空间内的变量i,所以它触发了闭包规则,然后函数multipliers()的返回值是一个列表,这个列表的元素为四个闭包函数名指向的内存地址,虽然for i in range(4)这段代码里面的i的值分别被赋予了 0 1 2 3这四个值,但是闭包函数res并没有引用这四个值,因为闭包函数此时此刻还没有被真正调用,列表推导式仅仅是把四个匿名函数指向的内存地址保存在了一个列表里,因为没有调用,所以匿名函数内部的代码并没有执行,也就不存在引用。
所以函数multipliers()的返回值就是这样的一个列表:[lambda x:ix,lambda x:ix,lambda x:ix,lambda x:ix]

我们来看最后一条语句print([m(2) for m in multipliers()])
for m in multipliers() 这条语句到底干了什么?其实它干的事情只有一个,那就是遍历了函数multipliers()返回的列表,在遍历列表的同时把每个匿名函数赋值给了m,把它拆分来看就是这样:
m = lambda x:i*x
m = lambda x:i*x
m = lambda x:i*x
m = lambda x:i*x
并且每次都执行了一次 m(2),也就是每次都调用了一下匿名函数,注意:此时此刻匿名函数才真正被调用了,然后它会引用外层命名空间的变量i,那么此时i的值是多少呢?
因为for i in range(4)这个for循环已经执行完毕,i的值等于3,所以每次当执行m(2)时,i的值都等于3
所以每次调用m(2)的结果都是6
最终输出结果为[6, 6, 6, 6]

def multipliers():
    return [lambda x:i*x for i in range(4)]
print([m(2) for m in multipliers()])
[6, 6, 6, 6]

把这道面试题中的所有列表推导式拆开的话 它应该是下面这个样子,结果完全一样:
def multipliers():
    squares = []
    for i in range(4):
        res = lambda x:i*x
        squares.append(res)
    return squares

#print(multipliers()),此时此刻如果我们打印一下这个函数,也就是调用一下看看返回结果,你会发现,它就是一个由四个函数内存地址组成的列表:
"""[. at 0x7fecdc6de2f0>, 
 . at 0x7fecdc6de510>, 
 . at 0x7fecdc6de158>, 
 . at 0x7fecdc6de268>]"""

squares2 = []
for m in multipliers():
    squares2.append(m(2))
print(squares2)
[6, 6, 6, 6]

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/42558.html

相关文章

  • python面试题之该死for循环系列”(一)

    摘要:这是一道魔性面试题,难倒了无数英雄好汉上面代码的执行顺序是这样的从上到下第一个函数就是实现了一个简单的加法运算第二个函数是一个生成器函数,如果调用它会返回一个生成器这一行调用了生成器函数,所以此刻就是一个生成器它的本质还是迭代器然后执行循环 这是一道魔性面试题,难倒了无数英雄好汉…… def add(n,i): return n+i def test(): for i...

    wudengzan 评论0 收藏0
  • 数据分析面试题之Pandas中groupby

    摘要:昨天晚上,笔者有幸参加了一场面试,有一个环节就是现场编程题目如下示例数据如下,求每名学生对应的成绩最高的那门科目与,用实现这个题目看上去很简单,其实,并不简单。   昨天晚上,笔者有幸参加了一场面试,有一个环节就是现场编程!题目如下:  示例数据如下,求每名学生(ID)对应的成绩(score)最高的那门科目(class)与ID,用Python实现: showImg(https://se...

    ThinkSNS 评论0 收藏0
  • 用9种办法解决 JS 闭包经典面试题之 for 循环取 i

    摘要:闭包正确的说应该是指一个闭包域每当声明了一个函数它就产生了一个闭包域可以解释为每个函数都有自己的函数栈每个闭包域对象都有一个不是属性内默认有个名为的全局引用有了这个引用就可以直接调用的属性或方法凡是在闭包域内声明的变量或方法外部无法直接访问 闭包 正确的说,应该是指一个闭包域,每当声明了一个函数,它就产生了一个闭包域(可以解释为每个函数都有自己的函数栈),每个闭包域(Function...

    Betta 评论0 收藏0
  • JS面试题之比较两个对象是否相等?

    摘要:这是我在一次面试中,被面试官所提问的一道题在这次面试题中相等指的是对象的属性个数值相等有这样两个李德华张德华我能想到的一种方案解答过程的思考由于没有,我只能通过转化成数组进入第二步,对象中的属性在另一个中是否存在。 这是我在一次面试中,被面试官所提问的一道题 在这次面试题中 相等:指的是对象的属性个数值相等 有这样两个obj let obj1 = { name:李德华, ...

    wangbjun 评论0 收藏0
  • 你不能错过前端面试题合集

    摘要:收集的一些前端面试题从面试题发现不足,进而查漏补缺,比通过面试更难得及各大互联网公司前端笔试面试题篇及各大互联网公司前端笔试面试题篇面试题个和个经典面试题前端开发面试题如何面试前端工程师很重要个变态题解析如何通过饿了么面试轻 收集的一些前端面试题 从面试题发现不足,进而查漏补缺,比通过面试更难得 1 BAT及各大互联网公司2014前端笔试面试题--Html,Css篇 2 BAT...

    ninefive 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<