6. 列表处理

序列(sequence)是 Python 中最基本的数据结构之一。序列中的每个元素都分配一个整数数字来唯一确定它的位置, 称为索引,第一个索引总是从0开始,第二个索引是1,依此类推。

Python有多个序列的内置类型,但最常见的是列表和元组,很多函数接受序列作为参数,比如 str.join()。

序列支持的操作包括索引,切片,加,乘,成员检查。此外,Python已经内置确定序列的长度以及确定最大和最小的元素的方法。

列表(list)作为序列类型之一是很常用的 Python 数据类型,它使用方括号包裹元素成员,成员间用逗号分隔,例如 [1, “abc”]。

列表的数据项不需要具有相同的类型。

6.1. 创建列表变量

6.1.1. 直接创建列表

注意:列表的数据项不需要具有相同的类型。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 每个值可以取相同类型的数据
list0 = [0, 1, 2, 3, 4]
list1 = [1.0, 2.0, 3.0]
list2 = ["12", "ab", "he"]

# 也可以取不同类型的数据,每个元素可以为任意数据类型
list3 = ["123", 1, 3.0, [1, 2], {"key": "val"}]
list4 = [list0, list1]

for i in range(5):
    print(eval("list" + str(i)))
>>>
[0, 1, 2, 3, 4]
[1.0, 2.0, 3.0]
['12', 'ab', 'he']
['123', 1, 3.0, [1, 2], {'key': 'val'}]
[[0, 1, 2, 3, 4], [1.0, 2.0, 3.0]]

6.1.2. 由列表组合生成新列表

“+” 运算符实现列表的拼接。

0
1
2
3
4
5
list0 = [0, 1, 2, 3, 4]
list1 = ['a', "bc", 1] + list0
print(list1)

>>>
['a', 'bc', 1, 0, 1, 2, 3, 4]

“*” 运算符实现列表的重复。

0
1
2
3
4
5
6
7
list0 = ['*'] * 5
list1 = [1, 2] * 5
print(list0)
print(list1)

>>>
['*', '*', '*', '*', '*']
[1, 2, 1, 2, 1, 2, 1, 2, 1, 2]

6.1.3. 列表推导

迭代语句方便对单个列表元素进行处理得到新列表,被称为列表推导(list comprehension),它是一种简化代码的方法。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
lista = [1, 2]
listb = ["ab", "cd"]
list0 = [x * 2 for x in lista]
list1 = [x[1] for x in listb]
list2 = [x + ".txt" for x in listb]

for i in range(3):
    print(eval("list" + str(i)))

>>>
[2, 4]
['b', 'd']
['ab.txt', 'cd.txt']

列表推导等价于如下过程:

0
1
2
3
4
5
6
listb = ["ab", "cd"]
list2 = [x + ".txt" for x in listb]

# 等价于
list2 = []
for x in listb:
    list2.append(x + ".txt")

6.1.4. 其他类型转换列表

list()内建函数实现其他类型向列表的转换。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 将字符串转换为每个字符组成额list
list0 = list("abcdef")
# 将元组转化为list类型
list1 = list((1, 2, 3))

# 将字典转换为 list,默认转换key到list,以下方式是等同的
dic0 = {"key0": "val0", "key1": "val1"}
list2 = list(dic0)
list3 = list(dic0.keys())

# 将字典的值转换为list
list4 = list(dic0.values())

for i in range(5):
    print(eval("list" + str(i)))

>>>
['a', 'b', 'c', 'd', 'e', 'f']
[1, 2, 3]
['key0', 'key1']
['key0', 'key1']
['val0', 'val1']

6.1.5. zip 合并

zip() 方法结合类型转换,可以巧妙的把两个或者多个链表中的元素一一对应成元组类型,生成新的元组列表。

0
1
2
3
4
list0 = list(zip(['one', 'two', 'three'], [1, 2, 3]))
print(list0)

>>>
[('one', 1), ('two', 2), ('three', 3)]

更复杂的操作参考 zip_longest

6.1.6. 复制列表

与字符串类似,列表作为序列类型,支持切片复制。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
list0 = ["ab", "cd"]
list1 = list0[:]
print(list1)

list1[0] = "AB"  # 改变 list1 不会影响 list0
print(list0)
print(list1)

>>>
['ab', 'cd']
['ab', 'cd']
['AB', 'cd']

列表的 L.copy() 方法与切片复制都是浅拷贝,只复制父对象一级,子对象不复制,还是引用。 如果要完全复制,需要借助 copy 模块进行深拷贝。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
list0 = [1, 2, [1, 2]]
list1 = list0.copy()   # 浅拷贝,与切片复制等同

import copy            # 深拷贝,完全复制
list2 = copy.deepcopy(list0)

list0[2][0] = "a"      # 不会改变深拷贝 list2
for i in range(3):
    print("list%d:\t%s" % (i, eval("list" + str(i))))

>>>
list0:  [1, 2, ['a', 2]]
list1:  [1, 2, ['a', 2]]
list2:  [1, 2, [1, 2]]

6.1.7. 深浅拷贝

int, float 和 complex 称为数值型类型,str 是字符类型,它们是 Python 中的基本数据类型,而 list 等其他数据类型为复合型数据类型,它们的元素可以嵌套,所以对这些复合类型分为深浅复制。

对于基本类型来说,一个值只有一个存储地址,所有拥有相同值的变量(名字)都指向它。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import copy
def with_sameid(inlist):
    idlist = map(lambda x:id(x), inlist)
    return len(set(idlist)) == 1

int0 = 1
int1 = int0
intlist = [int0, int1, copy.copy(int0), copy.deepcopy(int0)]
print(with_sameid(intlist))

float0 = 3.14
float1 = float0
floatlist = [float0, float1, copy.copy(float0), copy.deepcopy(float0)]
print(with_sameid(floatlist))

comp0 = 1j
comp1 = comp0
complist = [comp0, comp1, copy.copy(comp0), copy.deepcopy(comp0)]
print(with_sameid(complist))

str0 = 'abc'
str1 = str0
strlist = [str0, str1, copy.copy(str0), copy.deepcopy(str0)]
print(with_sameid(strlist))

>>>
True
True
True
True

对于list、tuple、dict 等复合类型类型,直接赋值给一个变量相当于引用,指向同一内存地址。浅拷贝,只拷贝一层,不拷贝父对象内部的子对象,列表的 L.copy() 方法,copy.copy() 和切片复制都是浅拷贝。深拷贝,递归拷贝所有子对象,使用 copy.deepcopy() 方法。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
list0 = [1, 2, [3.0, 3.1]]
list1 = list0
listlist = [list0, list1, list.copy(list0), copy.deepcopy(list0)]
for i in listlist:
    print(id(i), id(i[2]))

>>>
2101860280712 2101860020040
2101860280712 2101860020040 # 赋值相当于引用不变
2101860281096 2101860020040 # 二级元素地址,浅拷贝不变
2101859999240 2101860019656 # 深拷贝,所有地址改变

6.2. 访问列表中的值

6.2.1. 下标直接访问

通过下标直接取列表的单个元素,返回元素原来对应的类型。

0
1
2
3
4
5
6
7
8
list0 = [1, 2, 3, [4, 5]]
print(list0[0])    # 1
print(list0[-1])   # 4
print(type(list0[-1]))

>>>
1
[4, 5]
<class 'list'>

6.2.2. 切片取子列表

切片操作,取部分连续元素,返回列表类型,即便只取到一个元素。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
list0 = [1, 2, 3, 4]
print(list0[0:1])  # [1]
print(type(list0[0:1]))

print(list0[0:-1]) # 去掉尾巴元素的列表
print(list0[1:])   # 去掉头元素的列表

>>>
[1]
<class 'list'>
[1, 2, 3]
[2, 3, 4]

更详细切片操作参考 切片取子字符串

6.2.3. 过滤特定的元素

通过filter()函数提取特定元素生成新列表。

0
1
2
3
4
5
6
# 提取长度大于3的字符串元素
listc = ["abc", 123, "defg", 456]
list0 = list(filter(lambda s:isinstance(s, str) and len(s) > 3, listc))
print(list0)

>>>
['defg']

6.2.4. 枚举访问列表

enumerate()方法可以将列表转化为枚举对象,这样就很容易获得序列的编号。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
enumerate_obj = enumerate(['item0', 'item1', 'item2'])
for i, value in enumerate_obj:
    print(i, value)

print(type(enumerate_obj))

>>>
0 item0
1 item1
2 item2
<class 'enumerate'>

实际上,enumerate()方法可以将任意可迭代类型转化为枚举对象。

6.3. 索引访问和循环

字符串可以使用索引直接访问,列表也可以,所有的序列类型均可以使用索引访问,索引访问的本质是对象实现了 __getitem__() 方法。

这里实现一个可读写的字符串类型来分析通过下标进行读写的本质。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class RWStr():
    ## 一个可读写的字符串类

    def __init__(self, instr=''):
        self.instr = instr
        self.len = len(instr)

    def __setitem__(self, index, instr): # 实现写操作,支持字符和字符串插入
        if index > self.len:
            raise IndexError("list index out of range")

        tmpstr = self.instr[:index] + instr + self.instr[index:]
        self.instr = tmpstr
        self.len = len(tmpstr)
        print(self.len)

    def __getitem__(self, index):       # 读操作,支持索引和切片
        if isinstance(index, int):
            return self.instr[index]
        elif isinstance(index, slice):
            return self.instr[index]
        else:
            raise TypeError('Index must be int, not {}'.format(type(index).__name__))

rwstr = RWStr("hello")
print(rwstr[0])
print(rwstr[1:5])

rwstr[5] = " world!"

for i in rwstr:
    print(i, end=' ')

>>>
h
ello
12
h e l l o   w o r l d !

通过示例,也可以看出,只要实现了 __getitem__() 方法,就可以通过循环语句进行迭代读取处理。__setitem__() 方法对应写操作。

6.4. 列表统计

6.4.1. 统计元素个数

0
1
2
3
4
list0 = [1, 1, 2, [2, 3]]
print(len(list0))

>>>
4

6.4.2. 统计元素出现次数

0
1
2
3
4
list0 = [1, 1, 2, [2, 3]] # 注意 [2, 3] 是一个列表元素
print(list0.count(2))

>>>
1

6.4.3. 统计列表不同元素数

通过集合 set() 方法求交集。

注意:元素不能为复杂数据类型,比如列表,字典等。

0
1
2
3
4
5
list0 = [1, 1, 2, "abc"]
set0 = set(list0)
print(list(set0))

>>>
[1, 2, 'abc']

6.4.4. 统计最大最小值

max() 和 min() 方法可以得到列表中的最大和最小值。

注意:列表中元素必须均为数值,否则需要先转换为数值。

0
1
2
3
4
5
6
list0 = [1, 1, 2, 3.0]
print(max(list0))
print(min(list0))

>>>
3.0
1

6.5. 列表排序和反向

6.5.1. 列表排序

L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*

sort()函数直接对列表执行排序,无返回。注意:列表中元素类型必须相同。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 正序排列,直接对list操作
list0 = ['c1', 'b2', 'a0', 'd3']
list0.sort(reverse=False)
print(list0)

>>>
['a0', 'b2', 'c1', 'd3']

# 逆序排列
list0.sort(reverse=True)
print(list0)

>>>
['d3', 'c1', 'b2', 'a0']

# 可以指定key函数进行更复杂的排序
list0.sort(key=lambda x:x[1])
print(list0)

>>>
['a0', 'c1', 'b2', 'd3']

6.5.2. 列表反向

L.reverse() -- reverse *IN PLACE*

reverse()方法反向列表,元素颠倒。

0
1
2
3
4
5
list0 = [1, 2, 3, 4, ['a', 'b']]
list0.reverse()
print(list0)

>>>
['a', 'b'], 4, 3, 2, 1]

字符串可以借助列表反向函数,实现反向。

0
1
2
3
4
5
list0 = list("0123456")
list0.reverse()
print(''.join(list0))

>>>
6543210

6.6. 列表元素插入和扩展

6.6.1. 索引位置插入

L.insert(index, object) ->None -- insert object before index

在指定索引位置插入对象,其余元素后移,直接操作无返回。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
list0 = [0, 1, 2, 3]
list0.insert(2, 88)
print(list0)

# 如果索引超出list长度,则直接插入结尾
list0.insert(len(list0) + 1, [100, 101])
print(list0)

>>>
[0, 1, 88, 2, 3]
[0, 1, 88, 2, 3, [100, 101]]

6.6.2. 尾部追加

尽管通过 list.insert(len(list), object) 实现尾部追加,为了高效处理,Python 提供了专门的尾部追加函数 append()

L.append(object) -> None -- append object to end

append() 方法在列表尾部追加,直接操作无返回,参数作为整体插入为1个元素。

0
1
2
3
4
5
list0 = [0, 1, 2, 3]
list0.append([99,100])
print(list0)

>>>
[0, 1, 2, 3, [99, 100]]

L.extend(iterable) -> None -- extend list by appending elements from the iterable

列表的extend()方法可以接受一个迭代对象,并把所有对象逐个追加到列表尾部。

0
1
2
3
4
5
6
7
8
9
list0 = [0, 1, 2, 3]
list0.extend(["a", "b"])
print(list0)

list0.extend("123")
print(list0)

>>>
[0, 1, 2, 3, 'a', 'b']
[0, 1, 2, 3, 'a', 'b', '1', '2', '3']

6.7. 列表元素的删除

6.7.1. 根据索引删除

del()函数根据索引删除元素,支持切片操作,直接作用在列表上,无返回。

0
1
2
3
4
5
6
7
8
9
list0 = [1, 2, 2, 3, 4]
del(list0[0])    # 直接作用在list上
print(list0)

del(list0[0:3])  # 支持切片移除
print(list0)

>>>
[2, 2, 3, 4]
[4]

6.7.2. 根据索引删除并返回元素

L.pop([index]) -> item -- remove and return item at index (default last).
Raises IndexError if list is empty or index is out of range.

list的pop()方法删除指定索引元素并返回它,与append()配合可以实现队列或者堆栈。 如果索引超出范围,抛出 IndexError 异常。

0
1
2
3
4
5
6
7
8
list0 = [[1, 2], 2, 3, 4]
print(list0.pop()) # 默认参数index=-1,也即移除最后一个元素
print(list0.pop(0))
print(list0)

>>>
4
[1, 2]
[2, 3]

6.7.3. 根据元素值删除元素

L.remove(value) -> None -- remove first occurrence of value.
Raises ValueError if the value is not present.

remove()函数移除第一个匹配value值的元素,无返回。如果元素不存在,抛出 ValueError 异常。

0
1
2
3
4
5
list0 = [1, 2, 2, 3, 4]
list0.remove(2)
print(list0)

>>>
[1, 2, 3, 4]

6.8. 元素索引和存在判定

6.8.1. 获取元素索引

L.index(value, [start, [stop]]) -> integer -- return first index of value.
Raises ValueError if the value is not present.

index()方法可以在指定范围获取第一个匹配值的索引。如果值不存在则抛出 ValueError 异常。

0
1
2
3
4
5
6
list0 = [1, 2, 2, 4]
print(list0.index(2))      # 返回第一个匹配值2的元素索引
print(list0.index(2, 2, 3))# 在list[2:3 + 1]中查找第一个匹配2的元素索引

>>>
1
2

6.8.2. 判断元素是否存在

判断某元素是否存在使用 in 运算符;not in 运算符判断不存在,语句结果是布尔量。

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
list0 = [1, 2, 3, 5]
if 5 in list0:
  print(list0.index(5))

print(5 in list0)
print(5 not in list0)

>>>
3
True
False

也可以通过 index() 方法捕获异常的方式判定元素是否存在。

6.9. 列表比较

6.9.1. 直接使用比较运算符

比较运算费,又称为关系运算符,远算结果为布尔值。包括以下几种:

运算符 描述 实例
== 等于 - 比较对象是否相等 (a == b) 返回 False
!= 不等于 - 比较两个对象是否不相等 (a != b) 返回 true
> 大于 - 返回a是否大于b (a > b) 返回 true
< 小于 - 返回a是否小于b (a < b) 返回 true
>= 大于等于 - 返回a是否大于等于b (a >= b) 返回 False
<= 小于等于 - 返回a是否小于等于b (a <= b) 返回 true
  • == 和 != 运算符比较对象可以为任何不同的类型。
  • 含有 > 和 < 的运算符,比较对象类型必须相同。
 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
list0, list1 = [123, 'xyz'], [123, 'abc']
print(list0 > list1)
print(list0 == list0)
print(list0 == "123xyz")
print(list0 != 123)
print(list0 >= 123) # '>='不支持不同类型对象的比较

>>>
True
True
False
True

6.9.2. 使用用cmp()函数

注意:cmp()函数返回值为整型,已在3.0版本移除,它等价于 (a > b) - (a < b)。

0
1
2
3
4
5
6
7
print cmp(list0, list1)
print cmp(list1, list0)
print cmp(list1, list1)

>>>
1
-1
0

6.9.3. 使用 operator模块

operator模块提供的比较函数是运算符的另一种表达形式,它们之间是等价的。 比如 operator.lt() 函数与 a < b 是等价的。

0
1
2
3
4
5
operator.lt(a, b)  # '<'
operator.le(a, b)  # '<='
operator.eq(a, b)  # '=='
operator.ne(a, b)  # '!='
operator.ge(a, b)  # '>'
operator.gt(a, b)  # '>='

注意:还有一组带有下划线的函数,比如 operator.__lt__() 它们是为了向前兼容才保留的。

0
1
2
3
4
5
import operator
print(operator.eq(list0, list0))
print(operator.lt(list1, 0)) # '<'不支持不同类型对象的比较

>>>
True

6.10. 元组

元组(Tuple)与列表类似,它使用() 表示,元组兼容列表中大部分操作,比如索引,切片等。唯一不同在于元素只读,不可更改。

6.10.1. 元组的运算

如果元组只有一个元素,那么一定要加上逗号,由于() 本身是一个运算符,将直接返回括号内的内容。

0
1
2
3
4
5
print(type((1)))
print(type((1,)))

>>>
<class 'int'>
<class 'tuple'>

我们可以定义空元组,也可以追加元组到当前元组,所以只读是其中元素不可被删除或者更改,而不是元组自身不可更改。

0
1
2
3
4
5
6
7
8
9
tuple0 = ()
tuple0 += (1, 2, 3)
print(tuple0[0])
print(tuple0[0:2])
print(len(tuple0))

>>>
1
(1, 2)
3

可以将元组对象转换为其他类型:

0
1
2
3
4
5
6
# 类型转换
print(str(tuple0))
print(list(tuple0))

>>>
(1, 2, 3)
[1, 2, 3]

同样元组支持重复运算和拼接运算:

0
1
2
3
4
5
6
7
8
9
tuple0 *= 2            # 重复
print(tuple0)
tuple1 = (4, 5)

tuple0 += tuple1
print(tuple0)         # 拼接

>>>
(1, 2, 3, 1, 2, 3)
(1, 2, 3, 1, 2, 3, 4, 5)

元组是可迭代对象,支持 for in 操作,也可以作为函数的可迭代对象实参:

0
1
2
3
4
for i in tuple0:
    print(i)

# 作为可迭代对象实参
print(sum(tuple0))

不可更新元组元素的值:

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 不支持的操作
tuple0[4] = 0
tuple0[0] = 5
tuple0 += (4) # 注意与 tuple0 += (4,) 的区别

# 指向新对象
tuple0 = tuple1
print(tuple0)

>>>
(4, 5)

Python 内置模块 collections 扩展了普通元组,参考 namedtuple