Python 中的列表和字典一样,都是可变数据类型,与字符串和整型相比,它具有一些独特的特性。在平常使用中, 也会经常遇到一些坑,本文试着举一些例子并说明。
列表的拷贝
直接赋值
>>> a = [1,2,3]
>>> b = a
>>> a is b
True
>>> a[0]=5
>>> a
[5, 2, 3]
>>> b
[5, 2, 3]
在此例中,直接通过赋值将a
赋给了b
,此时,仅仅是为该列表增加了一个引用b
,a
与b
指向内存中同一个区域,通过a
改变列表的值也同时影响b
。请注意,这里有一个坑,很多人在初始化语句中写a = b = []
,这是错误的,会导致任意一个变动都会在a
与b
中同步,而且会很难 debug。正确写法应该是分别初始化。
使用list
工厂函数
为了创建一个a
的拷贝,可以使用list
工厂函数,这也是Python Cookbook中的推荐做法。
>>> a = [1,2,3]
>>> b = list(a)
>>> a is b
False
>>> a[0]=5
>>> a
[5, 2, 3]
>>> b
[1, 2, 3]
完美,a
和b
是两个不同的列表了!除了使用工厂函数,切片也可以达到同样的效果:
>>> b = a[:]
>>> b is a
False
使用copy
模块
一切看起来都很美好,真的是这样吗?
>>> a = [1,[1,2],3]
>>> b = list(a)
>>> a[0] = 5
>>> a[1][1] = 5
>>> a
[5, [1, 5], 3]
>>> b
[1, [1, 5], 3]
What?!b
的第二个元素子列表中的值还是被改变了!原来,list
和[:]
都是在内存中创建了一个新的对象并赋给了b
,但是子列表仍然只有一份。也就是说,只复制了「一层」。
为了解决这个问题,python 中自带了一个copy
模块专门做拷贝的事情,使用模块下的deepcopy
函数来深层次拷贝一个对象,调用它试试看:
>>> import copy
>>> b = copy.deepcopy(a)
>>> a[0] = 5
>>> a[1][1] = 5
>>> a
[5, [1, 5], 3]
>>> b
[1, [1, 2], 3]
妈妈再也不用担心我的列表交叉影响的问题了!
列表作为函数参数
参数的默认值
python 的函数参数传递方法都是引用传递,而不是值传递,对于列表与字典这种可变类型就要特别小心了,可能会出现以下的错误:
>>> def foo(a=[]):
... a.append(1)
... print a
...
>>> foo()
[1]
>>> foo()
[1, 1]
a
列表会保存上次调用之后的内容!因为这个列表在内存中创建以后就一直存在,参数a
默认指向这个对象。所以,要避免使用列表或字典作为函数的默认参数。使用下面的方法代替,只多一行,而且非常 pythonic:
def foo(a=None, b=None):
a = a or []
b = b or {}
...
更改传入列表的内容。
由于列表是可变的,你可以在函数体内增删元素,更改元素的值,从而影响到原列表。
>>> def foo(array):
... array.append(1)
...
>>> a=[0]
>>> foo(a)
>>> a
[0, 1]
然而有些时候,我们希望整体更新列表,比如去重操作array = list(set(array)
,这时用上面的方法就不行了,因为这里创建了一个新的列表list(set(array))
并将其引用重新赋给了array
,而函数内的局部变量array
的更改是无法影响全局变量的,这与上一例不同的时上个例子并没有改变array
的值,只是改变了array
指向的对象的值。
这时候,我们又要搬出切片了。只需要改成array[:] = list(set(array))
就可以了!因为切片本质上是对array
中元素的操作,意思是把list(set(array))
赋给array
中的所有元素。
>>> def unique(array):
... array[:]=list(set(array))
...
>>> a = [1, 2, 2, 3]
>>> unique(a)
>>> a
[1, 2, 3]