在我的回答有什么行为习惯昭示着你是个编程大佬?www.zhihu.com
中, @霍华德 师兄好奇为什么不要写
[0] * N
于是我便想结合Python的雷区,来解释一下。
什么是列表推导式?
列表推导式
index = [_ for _ in range(N)]
就是列表推导式,常用于代替循环,来创建规律性数组,比如[0, 1, 2, 3,...],属于Python的语法糖。那为什么 [0]* N 不太好呢?其实 [0]* N 跟 [0 for _ in range(N)] 完全等价,前者还更简洁啊。下面来说明原因。
基本数据类型和引用式拷贝
众所周知,Python中的所有数据都是对象,包括0, 1.0这样的整数or浮点数常量。然后Python的内存管理我也不太懂,就不深入了。只需要知道,0,1这样的整数基本单元(表现起来)是直接拷贝的。这是基本数据类型。
但是非基本数据类型,比如列表、字典,如果不调用copy()或者deepcopy(),那诸如使用 list2=list1, dict2=dict1的赋值式拷贝,统统都是传引用。也就是说,它们共享内存,改变list1/dict1中的元素,list2/dict2中的元素也一定会跟着改变。
好了,熟悉Python的人一定知道这些了,那么这个跟列表乘法操作和列表推导式有什么关系呢?下面来简单解释一下。
列表乘法操作和列表推导式
列表乘法操作并不是拷贝被乘的列表,而是拷贝了它的引用。
对于一维列表则没有雷区,如 [0, 1] * 3, 结果为 [0, 1, 0, 1, 0, 1],每个元素依然是独立的。
但是对于二维列表,雷区来啦ψ(`∇´)ψ!上代码:
def list_comp_test1():
"""list comprehension test function 1"""
N = 3
list1 = [0] * N
print(f"list1: {list1}")
# modify the first element
list1[0] = 1
print(f"list1: {list1}")
def list_comp_test2():
"""list comprehension test function 2"""
N = 3
list2 = [[0, 1]] * N # will copy by reference
print(f"list2: {list2}")
# modify the first element
list2[0][0] = 1
print(f"list2: {list2}")
def list_comp_test3():
"""list comprehension test function 2"""
N = 3
list3 = [[0, 1] for _ in range(N)] # will copy by truly copy
print(f"list3: {list3}")
# modify the first element
list3[0][0] = 1
print(f"list3: {list3}")
def main():
print("Now testing list multiply operation...")
list_comp_test2()
print("Now testing list comprehension...")
list_comp_test3()
if __name__ == "__main__":
main()
输出:
Now testing list multiply operation...
list2: [[0, 1], [0, 1], [0, 1]]
list2: [[1, 1], [1, 1], [1, 1]]
Now testing list comprehension...
list3: [[0, 1], [0, 1], [0, 1]]
list3: [[1, 1], [0, 1], [0, 1]]
注意差别,使用了列表乘法操作的list2,用 list2[0][0]=1 改变它的第一个元素的第一个元素,结果list2中的其余元素均被改变了。而使用列表推导式生成的list3,则没有这个问题!
这是因为list2中的每个元素(都是列表)拷贝的都是引用!而list3,由于是列表推导式,它的每个元素都是独立计算而成,占用独立的内存!
什么时候会踩雷
刷leetcode的同学一定会被坑过。比如创建一个最简单的图的邻接矩阵的时候(现在都用字典作为hash表来写了):
>>> a = [0, 0, 0, 0, 0, 0]
>>> a = [a] * 3
>>> a
[[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]
就会哦豁完蛋ψ(`∇´)ψ
>>> a[0][0] = 1
>>> a
[[1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0]]
结论
故想放心地生成元素在内存上独立的数组,还是用列表生成式比较好。
撰写仓促,很多概念的参考资料就不一一列举了,感兴趣的同学可以一起看Python文档。
参考