Python之列表

5.2列表(list)

不同类型的对象按序集中在一起就叫列表。列表的成员类型可以相同也可以不同,列表的大小不固定,列表的值是可变的(mutable),可以原地修改。它有以下特性:

列表是一种序列

可以根据索引号访问成员:因为列表位置有序,你可以根据索引号访问一个成员,你也可以做切片和连接操作。

长度可变,类型不同,任意嵌套:列表的长度可以原地改变,它可以包含各种不同类型的对象,甚至一些复杂的对象。它们也支持任意嵌套,比如可以创建列表的列表的列表(三层嵌套)。

列表是可变的:列表是可变的(可以原地修改,比如删除和通过索引赋值,这些操作字符串则没有),它可以象字符串一样进行序列操作,比如索引,切片和连接。切片和连接返回一个列表的浅拷贝

它实际上是一个数组,其成员是对其它对象的引用

5.2.1引用的概念

引用:在计算机中,我们要使用某一个数据,有两种方法,一种是直接使用数据本身,另一种是间接使用,即通过一个中间环节才能找到要使用的目标数据(Python中叫对象),我们称这个中间环节为目标对象的引用,这里的引用是一个名词。我们对引用(中间环节)的访问,实际上访问的是引用(中间环节)所指向的对象。

列表的元素实际是对下一级对象的引用

5.2.2列表有两种创建方式:

(1)用表达式创建,将一串对象写在一对方括号内,对象之间用逗号隔开,没有对象的方括号表示空列表。

[]#这是个空列表

>>>l = [1,['zwj','python']]

[1, ['zwj', 'python']]#它是一个嵌套式列表,而且成员的类型也不同。

也可以将已经存在的列表赋给另外一个名字(变量),实际上这个名字就成了这个列表的别名了。

[1, ['zwj', 'python']]

>>>l is l1

True

这说明,l与l1是同一个列表的不同名字而已。

(2)用构造list()函数创建

>>>l =list('python')

['p', 'y', 't', 'h', 'o', 'n']

5.2.3列表的操作

5.2.3.1作为序列的操作

因为列表是一种序列,所以它支持序列的操作。

(1)求列表长度操作

>>>len([1, ['zwj','python']])

2

(2)用+号连接两个列表

>>[1, ['zwj', 'python']]+['deep learning']

[1, ['zwj', 'python'], 'deep learning']

(3)用*号实现重复连接

现在有一个列表,只有一个成员,这个成员指向一个二级对象(空列表),然后将这个列表重复(实际上是浅拷贝)3次并连接起来。

>>>l = [[]]*3

[[], [], []]

我们来看下列表及元素的ID

>>>print(id(l),id(l[0]),id(l[1]),id(l[2]))

最后一行的意思是列表l位于起始地址为24366416的地址块中,它的三个成员都指向地址24298520,同一个二级对象。示意图如下:

现在有个问题,如果我们要改变列表l的第一个成员,结果会是什么?

(a)第一种情况是在二级列表中添加元素

列表l的第一个成员是一个空列表,它里面没有成员,现在想把它变成[3],这个可以用函数append(),此函数的作用是在列表的尾端添加一个项。

l[0]是列表l的第一项,它是个空列表,添加一个项,整数3.

>>>l[0].append(3)

[3]

是不是列表l变成了[[3],[],[]]呢?来看下

[[3], [3], [3]]

如果你是初学者,是不是有点出乎意料?到底是什么原因呢?

先把列表l地址与个元素的地址打印出来

>>> print(id(l),id(l[0]),id(l[1]),id(l[2]))

一级列表l存储位置不变,仍位于起始地址为24366416的地址块中,它的三个成员都仍然指向地址24298520,只是这个二级对象本来是空列表,现在添加了成员,这个成员引用了三级对象—整数3(或者说指向一个三级对象整数3)。

从上图看,一级列表的三个成员引用的都是同一个二级列表(地址是24298520)l[0].append(3)实际上是给这个二级列表添加了一个成员,它由空列表变成含有一个成员的列表,而这个成员引用了三级对象—整数3。

前面我们知道,这是一种间接访问,访问一级列表的成员时,实际上访问的是整数3。所以列表l就变成了[[3], [3], [3]]。

(b)第二种情况是通过索引号修改

>>>l[0] = 1

[1, [3], [3]]

>>> print(id(l),id(l[0]),id(l[1]),id(l[2]))

将列表l的第一个成员指向了新的二级对象整数1(起始地址变成了505991632)。

这时,列表l就变成了[[1], [3], [3]]。

到此,基本解释了列表原地修改的两种方法。

下面再继续探讨一下列表变长的问题:

>>>l[1].append(2)

[1, [3, 2], [3, 2]]

>>> print(id(l),id(l[0]),id(l[1]),id(l[2]))

为了透彻理解上述代码,来看下它的示意图。

列表从后面向前面看,最后一项的索引号为-1,接着是-2,例如

>>>l = [1,[3,2],[3,2]]

>>>print(l[-1],l[-2],l[-3])

[3, 2][3, 2] 1

(4)可以切片操作

用切片操作返回一个新的列表,也就是说按要求返回了一个浅拷贝

>>>l = [1,2,3]

[1, 2, 3]

>>> print(id(l),id(l[:]))

原列表与切片列表l[:]的ID是不一样的。

这里我们遇到了浅拷贝的概念,下面就来学习一下这个概念。

浅拷贝与深拷贝

浅拷贝:

在Python中,我们有两种拷贝对象的方式:浅拷贝和深拷贝。浅拷贝和深拷贝都可以在copy模块中找到,其中copy.copy()进行浅拷贝操作, copy.deepcopy()进行深拷贝操作。

浅拷贝是在另一块地址中创建一个新的对象,但是新对象的成员与源对象的成员引用的是同一个子对象。默认的拷贝是浅拷贝。

如果这时候我们修改源对象成员的属性,新对象中成员的属性也会改变。

在本例中,l2是l的浅拷贝。

可以看出l2是拷贝了列表l中对1,[‘zwj’,’python’],3的引用,却并没有拷贝1,[‘zwj’,’python’],3这三个对象。

前面列表的*号也是一种浅拷贝。

深拷贝:

为了获取一个和源对象的子对象没有任何关系的全新对象,我们需要深拷贝。

深拷贝是在另一块地址中创建一个新的对象,同时对象内的子对象也是新开辟的,和源对象对比仅仅是值相同。

Python的深拷贝很慢,原因在于深拷贝需要维护一个memo用于记录已经拷贝的对象。而维护这个memo的原因是为了避免相互引用造成的死循环。绝大多数情况下,程序中不存在相互引用。但作为通用模块,Python深拷贝必须为了这1%情形,牺牲99%情形下的性能。在使用时,我们可以通过自己实现

__deepcopy__方法来改进其效率。具体怎么改善,限于篇幅,这里不展开阐述。

5.2.3.2列表类特有的操作

前面我们介绍了函数的概念,而方法的定义形式与函数一样,只不过它是绑定在某些对象上的函数,对象可能是列表,数字,字符串,也有可能是后面要讲到的类的实例对象。方法的调用形式:

对象.方法(参数)

5.2.3.2.1列表的长度可以增长也可以缩短

>>>l = [1,'python',3.14]

>>>l.append('ok') #生长了:在列表尾端加了对象

[1, 'python', 3.14,'ok']

Append并不返回一个修改过的新列表,,而是直接在原地进行修改。

>>>l.pop(2) #缩短了,弹出列表的第二项

3.14

[1, 'python', 'ok']

Pop(i),弹出列表的第i个元素(默认的是最后一个元素),并返回该元素的值。Pop方法是唯一一个既能修改列表又返回元素值(除了None)的列表方法。

这可以实现栈操作,python可以用append方法实现入栈(push)操作。Pop方法和append方法的操作结果恰好相反,如果入栈刚刚出栈的值,最后得到的结果还是原来的栈。

>>>x = [1, 'python', 'ok']

>>>x.append(x.pop())

[1, 'python', 'ok']

如果要实现一个先进先出(FIFO)的队列,可以使用append方法,并用pop(0)来代替pop();也可以用insert(0,…)与pop()方法配对实现;更好的解决方法是使用collection模块的deque对象。

5.2.3.2.2列表的内置方法

remove

remove方法用于移除列表中某个值的第一个匹配项:

>>>x = ['to','be','or','not','to','be']

>>>x.remove('be')

['to', 'or', 'not', 'to', 'be']

remove是一个没有返回值的原位置修改方法,这点与pop方法不同。

reverse

reverse方法将列表中的元素反向存放

>>>x = [1,2,3]

>>>x.reverse()

[3, 2, 1]

此方法改变了列表但不返回值。

注意:如果需要对一个序列进行反向迭代,那么可以使用reversed函数,这个函数并不返回一个列表,而是返回一个迭代器(iterator),当然也可以使用函数list把返回的对象转换成列表也是可行的。

>>>x = [1,2,3]

>>>list(reversed(x))

[3, 2, 1]

Sort

Sort方法用于在原位置对列表进行排序,这意味着改变了原来的列表,但并不是返回一个已排序的列表副本。

>>>m = ['bb','aa','cc']

>>>m.sort()#按升序排列

['aa', 'bb', 'cc']

你可能想排好一个列表副本并保留原有的列表不变

对于初学者来说,你可能会这样想:

>>>m = ['bb','aa','cc']

>>>n = m.sort()

>>>print(n)#可能你期望的['aa', 'bb', 'cc'],但结果却是None

None

结果不是你要的,这是因为sort()方法并不返回一个已排序的列表副本,实际上它返回的是一个空值。

这时可以用切片的方法来实现

>>>m = ['bb','aa','cc']

>>>n = m[:]

>>>m.sort()

['aa', 'bb', 'cc']

['bb', 'aa', 'cc']

m[:]得到的是包含了m所有元素的分片,这是一个很有效率的浅拷贝。

初学者可能会简单地把m赋给n,这样只是让m,n指向了同一个列表。

>>>m = ['bb','aa','cc']

>>>m.sort()

['aa', 'bb', 'cc']

['aa', 'bb', 'cc']

也可以用sorted()函数返回已排序的列表拷贝:

>>>m = ['bb','aa','cc']

>>>n = sorted(m)

['aa', 'bb', 'cc']

['bb', 'aa', 'cc']

如果想把一些元素按相反的顺序排列,可以使用sort(或sorted())和reverse方法配合实现。

高级排序

Sort方法有另外两个可选的参数(key和reverse)

使用函数len作为键参数:

>>>x =['a','ab','abc','abcd']

>>>x.sort(key=len)

['a', 'ab', 'abc','abcd']

另一个关键字参数reverse是简单的布尔值(True或者False)用来指明列表是否要进行反向排序。

>>>m = [1,2,3,4,5]

>>>m.sort(reverse=1)

[5, 4, 3, 2, 1]

更多的关于排序的知识,请参考python手册。

5.2.3.2.3边界检查

虽然列表的长度不固定,但仍然不允许引用不存在的项。比如:

Traceback (most recentcall last):

File"

", line 1, in

l[99]

IndexError: list indexout of range

索引错误:列表的索引超出范围

>>>l[99] = 1

Traceback (most recentcall last):

File"

", line 1, in

l[99] = 1

IndexError: listassignment index out of range

索引错误:列表的赋值索引超出范围

5.2.3.2.4嵌套

前面其实我们已经接触了嵌套,就是列表的成员仍然是列表,嵌套深度可以根据我们的需要来定。这样我们就可以用嵌套列表来实现一个矩阵。

>>>m = [[1,2,3],

[4,5,6],

[7,8,9]]

[[1, 2, 3], [4, 5, 6],[7, 8, 9]]

一级列表m又包含了三个二级子列表,,效果就相当于一个矩阵。

>>>m[1]#相当于取出矩阵的第二行

[4, 5, 6]

>>>m[1][2]#相当于取出矩阵的第二行的第三列元素,如果是多层嵌套

6#我们就按这个形式操作

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180808G1OJ0R00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券