1、下面这段代码的输出结果是什么?请解释。
def extendList(val, list=[]):
list.append(val)
return list
list1 = extendList(10)
list2 = extendList(123,[])
list3 = extendList('a')
print(list1)
print(list2)
print(list3)
好吧,我知道使用可变对象作为函数参数会对后来的结果产生影响,但我还是做错了/捂脸。
答案是
[10, 'a']
[123]
[10, 'a']
那还是认真分析一波吧…
首先我们知道在 Python 中,函数的参数的传递都是引用传递(不管是可变对象还是不可变对象)。所以对于默认参数list=[]
,在编译的时候,实际上是先在堆内存开辟了一块内存mem1
用于存放空列表[]
,函数变量名list
指向内存块mem1
。
因此往后在没有给list
传递参数的情况下,list
默认指向mem1
。又因为函数return
的是list
,所有当执行到,
list1 = extendList(10)
相当于让list1
指向了默认参数list
指向的那块内存mem1
。
而当执行到下面这条语句,
list2 = extendList(123,[])
因为又给参数list
传递了一个空列表,注意前面所说的所有函数参数的传递都是引用传递,且这里的空列表和默认参数list=[]
的空列表是两个不同列表,所以list2
指向的是另一块内存mem2
。
最后执行到
list3 = extendList('a')
因为没有新的外界变量传进来,所以list3
指向的还是list1
指向的原先的那块内存mem1
。
所以最终结果就是…哈哈哈!
那怎么规避这种难以料及的错误的,事实上我们必须避免使用可变对象作为函数的参数。对于函数extendList()
的正确定义应该是:
def extendList(val, list=None):
if list is None:
list = []
list.append(val)
return list
2、下面这段代码的输出结果将是什么?请解释。
def multipliers():
return [lambda x : i * x for i in range(4)]
print([m(2) for m in multipliers()])
先来分析一波…
[lambda x : i * x for i in range(4)]
这是一个列表推导式,又因为 lambda 表达式返回一个函数对象,所以multipliers()
函数最终返回一个含有 4 个函数对象的列表。
[m(2) for m in multipliers()]
到了这里,则是从列表里一一取出函数对象,并分别传入参数2
,最终输出一个计算后的列表。所以答案是…/吐血
[6, 6, 6, 6]
懵了,怎么不是[0, 2, 4, 6]
好的,再来分析一波。
[lambda x : i * x for i in range(4)]
实际运行时,像下面这样(伪代码):
la = []
fetch = iter(range(4))
while True:
try:
i = next(fetch)
except StopIteration:
break
finally:
la.append(i*x)
可以看到,i
一直在被更新,期间的值分别等于 1、2、3。但对于la.append(i*x)
,传递的只是i
的引用,而且更重要的一点是函数对象i*x
没有马上执行,而是被添加到列表里。这导致前面添加的i*x
中的i
最终都指向了 3。
听起来很糟糕对吧!
那怎么避免这种问题呢?我们可以将i
设为 lambda 表达式的参数,从而创建一个闭包环境,由函数本身来维护它的变量。
def multipliers():
return [lambda x ,i=i : i * x for i in range(4)]
print([m(2) for m in multipliers()])
运行结果
[0, 2, 4, 6]
另外一种解决方法就是用 Python 生成器。
def multipliers():
for i in range(4):
yield lambda x : i * x
print([m(2) for m in multipliers()])
3、下面两段代码分别在 Python2、Python3下输出结果将是什么?请解释。
def div1(x,y):
print "%s/%s = %s" % (x, y, x/y)
def div2(x,y):
print "%s//%s = %s" % (x, y, x//y)
div1(5, 2)
div1(5., 2)
div2(5, 2)
div2(5., 2.)
def div1(x, y):
print("%s/%s = %s" % (x, y, x/y))
def div2(x, y):
print("%s//%s = %s" % (x, y, x//y))
div1(5, 2)
div1(5., 2)
div2(5, 2)
div2(5., 2.)
Python2
5/2 = 2
5.0/2 = 2.5
5//2 = 2
5.0//2.0 = 2.0
Python3
5/2 = 2.5
5.0/2 = 2.5
5//2 = 2
5.0//2.0 = 2.0
通常在 C/ C++ 中,/
算术运算符的计算结果是根据参与运算的两边数据的精度决定的。
在 Python2.2 版本以前也是这么规定的,但是,Python 的设计者认为这么做不符合 Python 简单明了的特性,于是乎就在 Python2.2 以及以后的版本中增加了一个算术运算符" // “来表示整数除法,返回不大于结果的一个最大的整数,而” / " 则单纯的表示浮点数除法。
所有 2.X 版本中,也是为了向后兼容,如果要使用//
,就必须加上一条语句:
from __future__ import division
注: 在 Python 3 中,/
操作符是做浮点除法,而 //
是做整除(即商没有余数,比如 10 // 3 其结果就为 3,余数会被截除掉,而 (-7) // 3 的结果却是 -3。这个算法与其它很多编程语言不一样,需要注意,它们的整除运算会向 0 的方向取值。
5、考虑下列代码片段
list = [ [ ] ] * 5
print(list)
list[0].append(10)
print(list)
list[1].append(20)
print(list)
list.append(30)
print(list)
将输出什么结果?试解释。
输出的结果如下:
[[], [], [], [], []]
[[10], [10], [10], [10], [10]]
[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20]]
[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20], 30]
解释如下:
第一行的输出结果直觉上很容易理解,例如 list = [ [ ] ] * 5 就是简单的创造了5个空列表。
然而,理解表达式list=[ [ ] ] * 5
的关键一点是它不是创造一个包含五个独立列表的列表,而是它是一个创建了包含对同一个列表五次引用的列表。只有了解了这一点,我们才能更好的理解接下来的输出结果。
6、给定一个含有 N 个数字的列表。
list = [1, 3, 5, 8, 10, 13, 18, 36, 78]
使用一行的列表推导式来生成一个新的列表,该列表只包含满足以下条件的元素:
a)偶数值
b)元素为原始列表中偶数切片。
例如,如果list[2]
包含的值是偶数。那么这个值应该被包含在新的列表当中。因为这个数字同时在原始列表的偶数序列(2为偶数)上。然而,如果list[3]
包含一个偶数,那个数字不应该被包含在新的列表当中,因为它在原始列表的奇数序列上。
思路,第一步取出偶数切片的数字,第二步剔除其中所有奇数。
代码如下
[x for x in list[: : 2] if x%2 == 0]
运行结果
[10, 18, 78]
[1] Python 面试中 8 个必考问题