python列表(list)和元组(tuple)浅析
列表(list)
和元组(tuple)
都是一种数据结构,python将这种数据结构统称为序列(sequence)
。和序列对应的就是映射(map)
,还有一种不属于这两种那就是集合(set)
。这三种其实都属于python的另一种数据结构,即容器(container)
。也就是说,python的容器是一种通用的数据结构,包括上面所说的这三种数据结构,它是一种包含其他对象的对象。其中序列
包含很多类型,常见的有list
, tuple
,list
中可以包含多种数据类型
的元素,比如:score这个序列中既包含字符串的name,也包含number类型的marks。
>>> zhangsan=['zhang san', 98]
>>> lisi=['li si', 60]
>>> score=[zhangsan, lisi]
>>> score
[['zhang san', 98], ['li si', 60]]
>>>
列表和元组的主要区别在于:列表可以修改,元组则不能。
序列的通用操作
所有的序列类型都可以进行某些特定的操作,这些操作包括:索引(indexing)
,分片(sliceing)
,加 (adding)
,乘(multipling)
,以及检查某个元素是否在序列中的in
,此外,python还提供了一些内建(build-in)函数,如:max
,min
, len
等。
索引操作(包括列表与元组)
接下来,我们看看索引操作
相关的例子,很明显它可以直接以变量的方式按索引取元素值,也可以直接以序列的方式取,还包括以函数返回值的方式取:
>>> greeting='hello'
>>> greeting[0] #以变量的方式
'h'
>>> greeting[-1]
'o'
>>> 'hello'[0] #以序列的方式
'h'
>>> 'hello'[-1]
'o'
>>> raw_input('Year: ')[3] #以函数返回值的方式
Year: 2017
'7'
切片操作(包括列表与元组)
接下来看一看切片操作的相关例子:
>>> numbers=[1,2,3,4,5,6,7,8,9,10]
>>> numbers[1,10]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: list indices must be integers, not tuple
>>> numbers[1:10]
[2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> numbers[1:10]
[2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> numbers[0:10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> numbers[:]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> numbers[5::2]
[6, 8, 10]
>>> numbers[5::-2]
[6, 4, 2]
>>> numbers[5::]
[6, 7, 8, 9, 10]
>>> numbers[5:]
[6, 7, 8, 9, 10]
>>> numbers[:2]
[1, 2]
>>> numbers[:2:]
[1, 2]
>>> numbers[-3:0]
[]
请仔细观察上面的各个例子,这样才能更好的理解以下几点:
- 分片以
:
分隔 - 分片从
0
开始计数索引 - 要取序列的首和末只需要分别将它们的位置留空
- 只要左索引比右索引晚出现在序列中,就是一个空序列
- 默认步长是1
- 如果没有左或者右索引,取数的顺序取决于它的步长是正还是负,正是顺,负是逆
加法操作(包括列表与元组)
我们来看看加操作,请看例子:
>>> [1,2,3]+[4,5,6]
[1, 2, 3, 4, 5, 6]
>>> 'hello,'+'world'
'hello,world'
>>> 'hello,'+[1,2,3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'list' objects
>>>
从例子中可见,加操作的两边必须是同类型,否则就出错。
请注意这个+
不会改变被加的序列(列表),而 list的 extend方法会。详见:
>>> a=[1,2,3]
>>> b=[4,5,6]
>>> a+b
[1, 2, 3, 4, 5, 6]
>>> a
[1, 2, 3]
>>> a.extend(b)
>>> a
[1, 2, 3, 4, 5, 6]
乘法操作(包括列表与元组)
再看看乘操作:
>>> 'love'*5
'lovelovelovelovelove'
>>> [1,2,3]*5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> [None]*5
[None, None, None, None, None]
>>> [1,2,3]*[1,2,3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'list'
>>> 'love'*'love'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'
>>>
请仔细观察上面的各个例子,这样才能更好的理解以下几点:
- 乘之前是什么类型还是该类型
- 乘操作只能以int类型作为参数
in 操作(包括列表与元组)
这里说一说in
运算符,对于早期python版本(<2.3),判断字符是否在字符串中可以用in
,但是这个只能用于判断单个字符,只有在2.3版本后,才可以允许多个字符的判断。
>>> 'h' in "hello"
True
>>> 'hell' in "hello"
True
>>> 3 in (1,2,3)
True
max和min函数(包括列表与元组)
看下max与min的内建函数:
>>> numbers=[1,9,4,2]
>>> max(numbers)
9
>>> min(numbers)
1
>>> max(1,9,4,2)
9
>>> min(1,9,4,2)
1
>>> max((1,2,3))
3
前两个操作好理解是基于序列的,但后两个操作很污啊,为什么直接传多个数值也可以啊,是因为如果用,
分隔值,则自动创建元组。所以逗号是很重要的,这个需要注意一下。
>>> 1,2,3
(1, 2, 3)
>>>
接下来分别对比讲一下列表及元组、字符串等。
只适合列表(list)的操作
列表(list)函数
list函数可以将一个不能修改的序列(如:字符串,元组),转换成list。
其实,list并不是函数,而是一种类型。下面我们看看如何来修改一个字符串,比如:
>>> str='python'
>>> str[0]='P'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> lst=list(str)
>>> lst
['p', 'y', 't', 'h', 'o', 'n']
>>> lst[0]='P'
>>> lst
['P', 'y', 't', 'h', 'o', 'n']
>>>
基本的列表(list)操作
基本的列表操作,包括元素赋值(=
),删除(del
), 分片赋值
>>> x=[1,2,3]
>>> x[1]=5
>>> x
[1, 5, 3]
>>> del x[1]
>>> x
[1, 3]
>>> x[1:1]=2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable
>>> x[1:1]=[2]
>>> x
[1, 2, 3]
>>> x[1:]=[]
>>> x
[1]
>>>
列表(list)的方法
列表(list),实际上是一个对象,那么它就有方法,调用的方法一般是 对象.方法()
的形式。注意,()
很重要,表示函数调用。它的方法有很多,比如:append, count, extend, index, insert, pop, remove, reverse, sort等,
append(...)
L.append(object) -- append object to end
count(...)
L.count(value) -> integer -- return number of occurrences of value
extend(...)
L.extend(iterable) -- extend list by appending elements from the iterable
index(...)
L.index(value, [start, [stop]]) -> integer -- return first index of value.
Raises ValueError if the value is not present.
insert(...)
L.insert(index, object) -- insert object before index
pop(...)
L.pop([index]) -> item -- remove and return item at index (default last).
Raises IndexError if list is empty or index is out of range.
remove(...)
L.remove(value) -- remove first occurrence of value.
Raises ValueError if the value is not present.
reverse(...)
L.reverse() -- reverse *IN PLACE*
sort(...)
L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*;
cmp(x, y) -> -1, 0, 1
这里主要列举一些比较容易出错的几点:
1、 pop是唯一的既修改了列表,又有返回值的方法
首先如何判断某个方法是否有返回值?
最简单直观的方法就是在python控制台敲该方法,如果回车后没有输出值就表示没有返回值,否则输出的该值就是有返回值。举例如下,reverse是没有返回值的,而pop有返回值。
>>> x=[1,2,3]
>>> x.reverse() #无输出
>>> x
[3, 2, 1]
>>> x=[1,2,3]
>>> x.pop() #有输出,3为其返回值
3
>>> x
[1, 2]
另外一种方法就是用 print打印出来,如果是None
则无返回值,否则就有。
>>> x=[1,2,3]
>>> print x.reverse() #返回值为None表示空
None
>>> x
[1, 2, 3]
>>> print x.pop(1) #返回值为2
2
>>> x
[1, 3]
>>>
2、 什么是原位置操作(in-place operation)?
原位置操作(in-place operation)表示:在执行某个操作后(一般是调用list的某个方法),改变了原列表的元素位置。即如果某个方法改变了原列表的元素位置,则该方法即是原位置方法。
>>> a=[1,2,3]
>>> b=[4,5,6]
>>> a+b #+号不是原位置操作
[1, 2, 3, 4, 5, 6]
>>> a
[1, 2, 3]
>>> a.extend(b) #extend是原位置操作方法
>>> a
[1, 2, 3, 4, 5, 6]
3、sort函数解密
现在我们来看看sort这个函数,它首先是一个原位置操作方法,会改变原列表的排序。
>>> numbers=[5,2,9,7]
>>> numbers.sort()
>>> numbers
[2, 5, 7, 9]
另外,它又不会返回值,所以想通过它来接收排序后的结果是得不到的。有时候我们经常希望保持排序前的数据不变,在调用sort后,然后将其赋值到某个列表,但这个想法是错误的,如下:
>>> x=[5,2,9,7]
>>> y=x.sort()
>>> x
[2, 5, 7, 9]
>>> print y
None
那要怎么办?看来不能直接通过返回值来接收,那我们先赋值呢?将y=x呢?
我们来看看:
>>> x=[5,2,9,7]
>>> y=x
>>> y
[5, 2, 9, 7]
>>> y.sort()
>>> y
[2, 5, 7, 9]
>>> x
[2, 5, 7, 9]
虽然实现了y的排序,但是x的值也跟着变了。。这并不是我们想要的。
我们很快会想到用分片操作,试试看:
>>> x=[5,2,9,7]
>>> y=x[:]
>>> y
[5, 2, 9, 7]
>>> y.sort()
>>> y
[2, 5, 7, 9]
>>> x
[5, 2, 9, 7]
果然可以了。原因是y=x这种赋值是一种引用的方法,是浅拷贝。而y=x[:]是深拷贝。
我们都知道,浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。具体一点,对于字符串类型来说,浅复制是对值的复制,对于对象来说,浅复制是对对象地址的复制,并没有开辟新的栈,也就是复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变,而深复制则是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
当然还有一种获取已排序的列表副本的方法是用sorted
,使用如下:
>>> x=[5,2,9,7]
>>> y=sorted(x)
>>> y
[2, 5, 7, 9]
>>> x
[5, 2, 9, 7]
好了,我们看看sort的函数原型,它有三个参数,
sort(...)
L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*;
cmp(x, y) -> -1, 0, 1
sort
函数可以接收的参数有:一种用于排序的比较函数 compare
(内建的是 cmp ,默认的也是cmp);一种用于按某种以关键字参数传递的key
参数,还有一种关键字参数是reverse
>>> numbers=[5,2,9,7]
>>> numbers.sort(cmp)
>>> numbers
[2, 5, 7, 9]
>>> numbers=['a5','b2aaaa1','9ccc','dd7']
>>> numbers.sort(key=len)
>>> numbers
['a5', 'dd7', '9ccc', 'b2aaaa1']
>>> numbers=['a5','b2aaaa1','9ccc','dd7']
>>> numbers.sort(key=len,reverse=True)
>>> numbers
['b2aaaa1', '9ccc', 'dd7', 'a5']
参数key和cmp有点类似,必须提供一个在排序过程中使用的函数。然而该函数并不像cmp那样直接确定对象的大小,而是为每个元素创建一个以赋值给key的函数的返回值的键,然后所有的元素按照键来排序。如上所示,就是根据元素的长度来进行排序的。
最后,cmp, key, reverse参数也都可用于sorted
的函数。
只适合元组(tuple)的操作
元组与列表一样,也是一种序列,唯一不同的是元组不能修改,其实字符串也是不能修改的。创建元组很简单,只要使用逗号隔开,自动就创建成了元组。
>>> 1,2,3
(1, 2, 3)
>>> 3,
(3,)
元组(tuple)函数
元组(tuple)和列表(list)一样,其实并不是函数,而是一种类型
。它以一个序列作为参数,并转化成为元组,如果本身就是元组,那么直接返回。
>>> tuple('Python')
('P', 'y', 't', 'h', 'o', 'n')
>>> tuple(('P', 'y', 't', 'h', 'o', 'n'))
('P', 'y', 't', 'h', 'o', 'n')
>>> tuple([1,2,3])
(1, 2, 3)
元组(tuple) 的基本操作
元组只读的,一般除了访问元数和分片外,没有什么操作。分片后当然还是元组。
>>> x=1,2,3
>>> x[1]
2
>>> x[:]
(1, 2, 3)
元组(tuple) 的作用是什么?
一般元组能做到的,列表也能做到,但下面两个重要原因,是列表不能替代的:
- 元组可以在映射或集合中充当键的作用,列表则不行。
- 元组作为很多内建函数和方法的返回值存在。