Python && 疑难杂症

可变对象作为函数参数

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()])

Python中除与整除

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 个必考问题

猜你喜欢

转载自blog.csdn.net/weixin_37641832/article/details/84979101