很多时候我们会用‘listn’的方式来快速得到一个重复元素的新list,比如:

[2]5 = [2, 2, 2, 2, 2]
[0,1]10 = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
[[0],1]10 = [[0], 1, [0], 1, [0], 1, [0], 1, [0], 1]
……

显然‘list×n’是将原list里面的所有元素整体复制n次后形成一个新的list。

但是特别注意:如果原list内部元素为“可变对象”(列表、字典、集合),则不会真正复制n份,而是传递n个引用给新的list,他们都指向原来的对象,当原list内部元素的元素改变时,新list内部对应元素也跟着变化(同样新list内部元素的元素变化时,原list也变化);如果原list内部元素为“不可变对象”(数字,字符串,元祖),则不存在这个问题。

下面用详细例子说明两者的区别:

1)list内部为可变对象

请看下面例子:

列表a的元素是一个列表(可变对象),b是通过a复制得到的新列表。

a = [['1']]
b = a * 3
print('a={}'.format(a))
print('b={}'.format(b))
# 输出:
a=[['1']]
b=[['1'], ['1'], ['1']]

下图展示了a和b之间的关系,可见b并没有独立复制一个对象,而和a指向同一个对象。


新建 Microsoft Visio 绘图.jpg

在前述代码的基础上通过a和b来修改内部列表的元素(注意是内部列表的元素),结果发现无论哪种情况,a和b都会同时变化:

a[0].extend([0]) #case 1
#a[0].append(0) #case 2
#a[0][0] = '2' #case 3
#b[0].extend([0]) #case 4
#b[0].append(0) #case 5
#b[0][0] = '2' #case 6
print('a={}'.format(a))
print('b={}'.format(b))
# 输出:
a=[['1', 0]]
b=[['1', 0], ['1', 0], ['1', 0]]
或
a=[['2']]
b=[['2'], ['2'], ['2']]

上述六种情况分两大类,其内部存储结构如下图,清晰解释了为什么a和b同时变化了。从图中可以看出,改变内部list(也就是图中的“中间list”)的元素,实际加上就是改变了最底层元素,而a和b是共享的,因此会同时变化。


新建 Microsoft Visio 绘图2.jpg

继续往下看,下例中只通过改变最外层list的元素,而不对内部list的元素操作,从结果可以看出,无论是单独改变a还是b,两者都不会相互影响,b内部元素之间也没有相互影响:

#case1
a[0] = ['4']
print('a={}'.format(a))
print('b={}'.format(b))
# 输出:
a=[['4']]
b=[['1'], ['1'], ['1']]
#case2
b[0] = ['4']
print('a={}'.format(a))
print('b={}'.format(b))
# 输出:
a=[['1']]
b=[['4'], ['1'], ['1']]

下图展示了为什么会出现这种结果,a[0] = ['4']实际就是将a[0]指向了一个新的对象,而b中所有元素仍然指向原来的中间list,因此a变了,b没有变。同理b[0] = ['4']也只是将b[0]指向了一个新的对象,而b中的其他元素和a都还指向原来的对象,因此也不会变。


新建 Microsoft Visio 绘图3.jpg

前述例子中最外层list的的内部元素是list,如果内部元素换成字典或集合,结果也一样,比如字典(其内部存储机制与前面完全相同):

a = [{'age':10}]
b = a*3
print('a={}'.format(a))
print('b={}'.format(b))
# 输出,这没问题
a=[{'age': 10}]
b=[{'age': 10}, {'age': 10}, {'age': 10}]
#通过a和b来改变他们内部元素的值,即改变字典的值
a[0]['age'] = 20
#b[0]['age'] = 20
print('a={}'.format(a))
print('b={}'.format(b))
# 输出,可见,a和b同时变了
a=[{'age': 20}]
b=[{'age': 20}, {'age': 20}, {'age': 20}]
#通过将a和b元素指向新的元素,
a[0] = {'name':'Lili'}
print('a={}'.format(a))
print('b={}'.format(b))
# 输出,可见,只有a变了,
a=[{'name': 'Lili'}]
b=[{'age': 20}, {'age': 20}, {'age': 20}]
或
a=[{'age': 20}]
b=[{'name': 'Lili'}, {'age': 20}, {'age': 20}]

2)list内部为不可变对象

前述情况中,list内部的元素都是可变对象(列表,字典、集合),例如[[],[],[]]或[{},{},{}]

如果内部元素是不可变对象(字符串,数字,元组),例如['a','b']或[1,3],那问题就简单了,完成不存在前述情况,内部存储结构也很简单。

请看下例:

a = ['cgx']
b = a*3
print('a={}'.format(a))
print('b={}'.format(b))
# 输出,这没问题
a=['cgx']
b=['cgx', 'cgx', 'cgx']

上述过程的存储过程如下:


新建 Microsoft Visio 绘图.jpg

我们企图通过下面方式来改变内部元素是行不通的,可以通过a[0][0]或b[0][0]来得元素“c”,但企图通过 a[0][0] = 'C'和b[0][0] = 'C'来将原来的‘c’变为“C”,这是行不通的,因为字符串属于不可变对象,python禁止这种操作。

a[0][0] = 'C'
b[0][0] = 'C'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
 in 
4 print('b={}'.format(b))
5
----> 6 a[0][0] = 'C'
7 b[0][0] = 'C'
8 # print('a={}'.format(a))
TypeError: 'str' object does not support item assignment

如果只是改变最外层元素(或者说将最外层list元素指向新的对象),那是没什么问题的,这跟前面可变对象的情况没什么区别,比如:

a[0] = 'Li'
#b[0] = 'Li'
print('a={}'.format(a))
print('b={}'.format(b))
#输出
a=['Li']
b=['cgx', 'cgx', 'cgx']
或
a=['cgx']
b=['Li', 'cgx', 'cgx']

可见,通过a[0] = 'Li'或b[0] = 'Li'的操作,仅仅只是改变了a和b最外层单个元素的指向,不会影响其他元素。该过程如下图所示:


新建 Microsoft Visio 绘图.jpg

当list的内部元素为“数字”或“元组”时,情况与上述‘字符串’完全相同,此处不再赘述。

总结:

old_list×n = new_list 得到新的list,应该特别注意:

(1) old_list内部的元素是“可变对象”(列表、字典、集合)时,无论是从old_list,还是从new_list 中改变“元素”或“健:值”(通常是利用old_list[][]=或new_list[][]=这种形式操作),都会对所有的对象产生影响;而如果只是对最外层list的对象进行操作(包括替换,重新赋值等,通常是old_list[]=或new_list[]形式),则只影响被操作的对象,不会影响所有对象。

old_list = [[元素,],[元素,],……]

old_list = [{健:值},{健:值},……]

……

(2) old_list内部的元素是“不可变对象”(字符串、数字、元组)时,不存在改变内部元素值的问题(比如下面将‘ab’中的a变为A,这是python不允许的),也就是说old_list[][]=或new_list[][]=这种操作形式不存在。但对最外层list的对象进行操作(包括替换,重新赋值等,通常是old_list[]=或new_list[])与(1)的情况完全相同。可见对于不可变对象而言,情况要简单。

old_list = ['ab','c',……]
old_list = [1,22,……]
old_list = [(1,2),(3,8),……]
……