python长度为n的list_python “list*n”的坑你得注意

探讨Python中使用列表复制(list×n)时遇到的问题,特别是当列表包含可变对象(如列表、字典)时的行为差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

很多时候我们会用‘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),……]

……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值