Published on

Python 列表小技巧

关于列表的一些可能遇到的坑

Reading Time

5 min read

Python 中的列表和字典一样,都是可变数据类型,与字符串和整型相比,它具有一些独特的特性。在平常使用中, 也会经常遇到一些坑,本文试着举一些例子并说明。

列表的拷贝

直接赋值

>>> a = [1,2,3]
>>> b = a
>>> a is b
True
>>> a[0]=5
>>> a
[5, 2, 3]
>>> b
[5, 2, 3]

在此例中,直接通过赋值将a赋给了b,此时,仅仅是为该列表增加了一个引用bab指向内存中同一个区域,通过a改变列表的值也同时影响b。请注意,这里有一个坑,很多人在初始化语句中写a = b = [],这是错误的,会导致任意一个变动都会在ab中同步,而且会很难 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]

完美,ab是两个不同的列表了!除了使用工厂函数,切片也可以达到同样的效果:

>>> 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]
Share: