【Python学习笔记】 第8章 列表与字典

列表

Python的列表是:

  • 任意对象的有序集合

  • 通过偏移访问

  • 可变长度、异构以及任意嵌套

  • 属于“可变序列”的分类

  • 对象引用数组

下表是常见/具有代表性的列表对象操作:

操作 解释
L = [] 一个空的列表
L = [123, 'abc', 1.23, {}] 有四个项的列表,索引从0到3
L = list('spam') 一个可迭代对象元素的列表
L = list(range(-4, 4)) 连续整数的列表
L[i] 索引
L[i][j] 索引的索引(用于列表嵌套列表)
L[i:j] 分片
len(L) 求长度
L1 + L2 拼接
L * 3 重复
for x in L: print(x) 迭代
3 in L 成员关系
L.append(4) 尾部添加
L.extend([5, 6, 7]) 尾部扩展
L.insert(i, X) 插入
L.index(X) 索引
L.count(X) 统计元素出现个数
L.sort() 排序
L.reverse() 反转
L.copy() 复制
L.clear() 清除
L.pop(i) 删除i处元素,并将其返回
L.remove(X) 删除元素X
del L[i] 删除i处元素
del L[i:j] 删除ij处的片段
L[i:j] = [] 删除ij处的片段
L[i] = 3 索引赋值
L[i:j] = [4, 5, 6] 分片赋值
L = [x ** 2 for x in range(5)] 列表推导和映射
list(map(ord, 'spam')) 构造列表

列表的字面量表达式是一系列对象,括在方括号里并用逗号隔开。上面的表中的一些操作与字符串类似。

列表的实际应用

基本列表操作:

len求长度、+拼接、*重复:

>>> len([1, 2, 3])
3
>>> [1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6]
>>> ['Ni!'] * 4
['Ni!', 'Ni!', 'Ni!', 'Ni!']

这里要求+的两边必须是列表,否则会出现类型错误。

列表迭代与推导

列表对所有序列操作都能做出相应,包括for

>>> 3 in [1, 2, 3]          # 3是否是列表的成员
True
>>> for x in [1, 2, 3]:     # 迭代
...     print(x, end=' ')
...
1 2 3

for会从左到右地遍历任何序列中的项,对每一项执行每一条子句(在for语句后输入提示符为...的若干行)。

列表推导:通过对序列中的每一项应用一个表达式来构建新的列表的方式,用到for

>>> res = [c * 4 for c in 'SPAM']
>>> res
['SSSS', 'PPPP', 'AAAA', 'MMMM']

相较于使用for语句构造列表,列表推导更简单。map也能实现类似效果,它对序列中的每一项应用一个函数,并把结果收集到一个新的列表中:

>>> list(map(abs, [-1, -2, 0, 1, 2]))
[1, 2, 0, 1, 2]

索引、分片和矩阵

索引和分片的操作与字符串相同。但是,列表索引的结果是指定偏移处的对象,而分片返回新列表:

>>> L = ['spam', 123, [1, 2, 3]]
>>> L[2]
[1, 2, 3]
>>> L[-2]
123
>>> a = L[2]
>>> L[2][1] = 4
>>> a
[1, 4, 3]

由于列表可以嵌套列表,我们有时候需要将几次索引操作连在一起,以矩阵为例:

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

原位置修改列表

列表是可变的,支持原地修改。

索引与分片的赋值

可以将一个对象赋值给一个特定项(偏移)或整个片段(分片)来改变它的内容:

>>> L = ['spam', 'Spam', 'SPAM!']
>>> L[1] = 'eggs'
>>> L
['spam', 'eggs', 'SPAM!']
>>> L[0:2] = ['eat', 'more']
>>> L
['eat', 'more', 'SPAM!']

分片赋值可以分成两步理解。插入:删除等号左边指定的分片;插入:将包含在等号右边的可迭代对象中的片段插入旧分片被删除的位置。这意味着,等号左边分片的长度不一定要等于右边列表的长度。

这样,分片赋值可以写成插入或删除操作。

>>> L = [1, 2, 3]
>>> L[1:2] = [4, 5] # 替换
>>> L
[1, 4, 5, 3]
>>> L[1:1] = [6, 7] # 插入
>>> L
[1, 6, 7, 4, 5, 3]
>>> L[1:2] = []     # 删除
>>> L
[1, 7, 4, 5, 3]

也可以用于在列表头部拼接:

>>> L = [1]
>>> L[:0] = [2, 3, 4]
>>> L
[2, 3, 4, 1]
>>> L[len(L):] = [5, 6, 7]
>>> L

分片替换是强大的功能,但我们更经常用replaceinsert等。

列表方法调用

Python列表支持特定类型方法调用,这些方法可以原地修改列表。其中append方法在列表末尾添加单一对象(不生成新的列表),sort方法给列表中的内容排序。

>>> L = ['eat', 'more', 'SPAM!']
>>> L.append('please')
>>> L
['eat', 'more', 'SPAM!', 'please']
>>> L.sort()
>>> L
['SPAM!', 'eat', 'more', 'please']

更多关于列表排序

在默认情况下,sort使用Python的默认比较(这里是字符串比较),以及升序排序。可以通过关键词参数修改sort。关键词参数是函数调用中的一种特殊的name=value语法,通过名字传递。

sort中,reverse指定排序是升序的,还是降序的;key参数返回在排序中使用的值,让sort根据key(元素)的值给元素排序。

>>> L = ['abc', 'ABD', 'aBe']
>>> L.sort()
>>> L
['ABD', 'aBe', 'abc']
>>> L.sort(key=str.lower)
>>> L
['abc', 'ABD', 'aBe']
>>> L.sort(key=str.lower, reverse=True)
>>> L
['aBe', 'ABD', 'abc']

注意,appendsort修改原位置的列表对象,但不返回列表。如果我们要得到一个新的、重新排序的列表,并且要返回这个列表,那么要用sorted(),他不会原地修改列表。

>>> L = ['abc', 'ABD', 'aBe']
>>> sorted(L, key=str.lower, reverse=True)
['aBe', 'ABD', 'abc']

其他常见的列表方法

reverse原地反转列表,extend在末端插入多个元素,pop在末端删除一个元素并返回这个元素。当然,也有reversed函数返回新的对象。

>>> L = [1, 2]
>>> L.extend([3, 4, 5])
>>> L
[1, 2, 3, 4, 5]
>>> L.pop()
5
>>> L
[1, 2, 3, 4]
>>> L.reverse()
>>> L
[4, 3, 2, 1]
>>> list(reversed(L))
[1, 2, 3, 4]

append也可以在尾部添加元素,但只能添加一个:

>>> L = []
>>> L.append(1)
>>> L.append(2)
>>> L
[1, 2]

pop可以接受偏移量,指定要删除的元素。remove通过值删除元素,insert在特定位置插入元素,count计算某元素的出现次数,index查找某元素第一次出现在列表中的位置。

>>> L = ['spam', 'eggs', 'ham']
>>> L.index('eggs')
1
>>> L.insert(1, 'toast')
>>> L
['spam', 'toast', 'eggs', 'ham']
>>> L.remove('eggs')
>>> L
['spam', 'toast', 'ham']
>>> L.pop(1)
'toast'
>>> L.count('spam')
1

其他常见列表操作

del可以删除索引或分片:

>>> L = ['spam', 'eggs', 'ham', 'toast']
>>> del L[0]
>>> L
['eggs', 'ham', 'toast']
>>> del L[1:]
>>> L
['eggs']

这可以通过将一个分片L[i:j]赋值为空列表来实现。

L.copy()方法、L[:]list(L)都能实现列表的顶层复制。

字典

Python中的字典的主要属性:

  • 通过键而不是偏移量来读取

  • 任意对象的无序集合

  • 长度可变、异构、任意嵌套

  • 属于“可变映射”类型(不支持序列操作)

  • 对象引用表(散列表)

常见/具有代表性的字典操作:

操作 解释
D = {} 空字典
D = {'name': 'Bob', 'age': 40} 有两个元素的字典
E = {'cto': {'name': 'Bob', 'age': 40}} 嵌套
D = dict(name='Bob', age=40) 通过关键字参数构造字典
D = dict([('name', 'Bob'), ('age', 40)]) 通过键值对构造字典
D = dict(zip(keylist, valslist)) 通过拉链式键值对构造字典
D = dict.fromkeys(['a', 'b']) 键列表
D['name'] 通过键索引
E['eto']['age'] 嵌套索引
'age' in D 成员关系:是否存在键
D.keys() 所有键
D.values() 所有值
D.items() 所有“键+值”元组
D.copy() 复制
D.clear() 删除所有内容
D.update(D2) 通过键合并
D.get(key, default?) 通过键获取,如果不存在默认返回None
D.pop(key, default?) 通过键删除,如果不存在返回错误
D.setdefault(key, default?) 通过键获取,如果不存在默认设置为None
D.popitem() 删除/返回所有的键值
len(D) 长度(键值对的对数)
D[key] = 42 新增/修改键
del D [key] 根据键删除条目
list(D.keys()) 查看字典键
D1.keys() & D2.keys()
Dictionary views 查看字典键
D = {x: x * 2 for x in range(10)} 字典推导

字典的实际应用

字典的基本操作

通常情况下,创建字典并通过键来存储、访问其中的某项:

>>> D = {'spam': 2, 'ham': 1, 'eggs': 3}
>>> D['spam']
2
>>> D
{'spam': 2, 'ham': 1, 'eggs': 3}

这里的字典是D,键'spam'的值为2,我们用方括号语法,用键对字典进行索引操作。

内置的len也适用于字典,返回存储在字典中的元素的数目(键的数目);字典的in成员关系运算符检查键是否在字典中;keys方法返回列表中所有的键。

>>> len(D)
3
>>> 'ham' in D
True
>>> list(D.keys())
['spam', 'ham', 'eggs']

注意,keys方法不返回列表,而是返回dict_keys类型,因此需要list转换成列表。

原位置修改字典

字典也是可变的,因此可以在原位置对其修改、增大以及缩短。只需要给一个键赋值就可以改变或者生成元素。del也适用于字典,用于删除作为索引的键相关联的元素。字典支持所有类型,包括嵌套列表。

>>> D
{'spam': 2, 'ham': 1, 'eggs': 3}
>>> D['ham'] = ['grill', 'bake', 'fry'] # 改变索引对应的值
>>> D
{'spam': 2, 'ham': ['grill', 'bake', 'fry'], 'eggs': 3}
>>> del D['eggs']   # 删除元素
>>> D
{'spam': 2, 'ham': ['grill', 'bake', 'fry']}
>>> D['brunch'] = 'Bacon'   # 生成元素(原来的字典没有`'brunch'`键)
>>> D
{'spam': 2, 'ham': ['grill', 'bake', 'fry'], 'brunch': 'Bacon'}

其他字典方法

字典的valuesitems方法分别返回字典的所有值列表和(key, value)对元组。这两个方法返回可迭代对象,可以将其转换为列表。

>>> D = {'spam': 2, 'ham': 1, 'eggs': 3}
>>> list(D.values())
[2, 1, 3]
>>> list(D.items())
[('spam', 2), ('ham', 1), ('eggs', 3)]

读取不存在的键会出错,但是通过get方法读取不存在的键会返回None或自定义的值。这对于不知道键是否存在的时候非常有用。

>>> D['spam']
2
>>> D.get('spam')
2
>>> D['toast']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'toast'
>>> D.get('toast')
>>> D.get('toast')
>>> print(D.get('toast'))
None
>>> print(D.get('toast', 0))
0

update方法类似于拼接,把一个字典的键和值拼接到另一个字典中。

>>> D
{'spam': 2, 'ham': 1, 'eggs': 3}
>>> D2 = {'spam': 3, 'toast': 4, 'muffin': 5}
>>> D.update(D2)
>>> D
{'spam': 3, 'ham': 1, 'eggs': 3, 'toast': 4, 'muffin': 5}

pop方法删除一个键并返回值。

>>> D
{'spam': 3, 'ham': 1, 'eggs': 3, 'toast': 4, 'muffin': 5}
>>> D.pop('muffin')
5
>>> D.pop('toast')
4
>>> D
{'spam': 3, 'ham': 1, 'eggs': 3}

字典用法注意事项

  • 序列运算无效:字典的元素没有“顺序”的概念,因此拼接、分片无效;
  • 对新索引赋值会添加项
  • 键不一定总是字符串

用字典模拟灵活的列表:整数键

用较大的数作为偏移值修改列表中的值会报错,但这种情况不会再字典中出现。

>>> L = []
>>> L[99] = 'spam'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list assignment index out of range
>>> D = {}
>>> D[99] = 'spam'
>>> D
{99: 'spam'}

用整数作为键,我们可以把字典看作更具有灵活性的表。

对稀疏数据结构使用字典:用元组作键

字典键也常用于实现稀疏数据结构。

>>> Matrix = {}
>>> Matrix[(2, 3, 4)] = 88
>>> Matrix[(7, 8, 9)] = 99
>>> X = 2
>>> Y = 3
>>> Z = 4
>>> Matrix[(X, Y, Z)]
88
>>> Matrix
{(2, 3, 4): 88, (7, 8, 9): 99}

这种方法可以只存储两个有值的点,而不是整个三维数组;缺点是不能访问非存储点的坐标。

避免键不存在错误

可以用iftry方法检测值是否存在(这两个方法在第10章学习)。


>>> if (2, 3, 6) in Matrix:
...     print(Matrix[(2, 3, 6)])
... else:
...     print(0)
...
0
>>> try:
...     print(Matrix[(2, 3, 6)])
... except KeyError:
...     print(0)
...
0

但是最简洁的办法是get

>>> Matrix.get((2, 3, 4), 0)
88
>>> Matrix.get((2, 3, 6), 0)
0

字典的嵌套

一般来说,字典可以取代搜索数据结构,并可以表示多种结构化信息。

Python的内置数据类型可以很轻松地表达结构化信息,下面代码可以一次性地写好字典,而不是分开对每个键赋值:

>>> rec = {'name': 'Bob',
...        'jobs': ['developer', 'manager'],
...        'web': 'www.bobs.com/?Bob',
...        'home': {'state': 'Overworked', 'zip': 12345}}

访问嵌套对象地元素时,只要简单地把连续地索引操作用起来:

>>> rec['jobs'][1]
'manager'
>>> rec['home']['zip']
12345

创建字典地其他方式

创建字典有四种方式:

>>> # 第一种
>>> {'name': 'Bob', 'age': 40}
>>> # 第二种
>>> D = {}
>>> D['name'] = 'Bob'
>>> D['age'] = 40
>>> # 第三种
>>> dict(name='Bob', age=40)
>>> # 第四种
>>> dict([('name', 'Bob'), ('age', 40)])

适用条件:

  • 事先可以拼出整个字典:第一种;
  • 需要一次动态地建立字典地一个字段:第二种;
  • 键是字符串:第三种;
  • 通过序列构建字典:第四种。

最后一种形式会与zip一起用,把程序运行时动态获取的单独键列表和单独值列表一一对应拼接在一起:

dict(zip(keyslist, valueslist))

如果要所有键对应的值相同,我们可以用fromkeys方法初始化,传入一个键的列表和一个初始值:

>>> dict.fromkeys(['a', 'b', 'c'], 0)
{'a': 0, 'b': 0, 'c': 0}

请留意:字典vs列表

列表将元素赋值给位置,而字典将元素赋值给更加便于记忆的键。在实际生活中,字典适用于存储带有标签的数据,或者是通过名称直接快速查询的结构。

Python 3.X和2.7中的字典变化

Python 3.X中的列表:

  • 支持新的字典推导表达式
  • 对于D.keysD.valuesD.items方法,不是返回列表,而是其他可迭代对象
  • 不再直接支持相对大小的比较,而是手动比较
  • 没有has_key,改为in成员关系测试

3.X和2.7的字典推导

我们可以用zip内置函数从值和键的列表中构造字典。

>>> D = dict(zip(['a', 'b', 'c'], [1, 2, 3]))
>>> D
{'a': 1, 'b': 2, 'c': 3}

在3.X和2.7中,我们可以用一个字典推导表达式来代替:

>>> D = {k: v for (k, v) in zip(['a', 'b', 'c'], [1, 2, 3])}
>>> D
{'a': 1, 'b': 2, 'c': 3}

我们可以使用推导把单独的一串值映射到字典,可以用表达式计算:

>>> D = {x: x ** 2 for x in [1, 2, 3, 4]}
>>> D
{1: 1, 2: 4, 3: 9, 4: 16}
>>>
>>> D = {c: c * 4 for c in 'SPAM'}
>>> D
{'S': 'SSSS', 'P': 'PPPP', 'A': 'AAAA', 'M': 'MMMM'}

我们可以用这种字典推导,将所有键初始化到同一个值。

3.X中的字典视图

在3.X中,字典的keysvaluesitems返回视图对象,在2.X中返回列表。视图对象是可迭代对象,但不是列表。

运行这三个方法的结果:

>>> D = dict(a=1, b=2, c=3)
>>> D
{'a': 1, 'b': 2, 'c': 3}
>>> D.keys()
dict_keys(['a', 'b', 'c'])
>>> list(D.keys())
['a', 'b', 'c']
>>> D.values()
dict_values([1, 2, 3])
>>> list(D.values())
[1, 2, 3]
>>> D.items()
dict_items([('a', 1), ('b', 2), ('c', 3)])
>>> list(D.items())
[('a', 1), ('b', 2), ('c', 3)]

Python 3.X的字典自己就有迭代器,它返回连续键。

>>> for key in D: print(key)
...
a
b
c

Python 3.X的字典视图可以动态地反映视图对象创建之后对字典做出的修改:

>>> K = D.keys()
>>> V = D.values()
>>> K, V
(dict_keys(['a', 'b', 'c']), dict_values([1, 2, 3]))
>>> del D['b']
>>> D
{'a': 1, 'c': 3}
>>> K
dict_keys(['a', 'c'])
>>> V
dict_values([1, 3])

字典视图和集合

字典的keys可以视为集合,但values不能。

>>> K, V
(dict_keys(['a', 'c']), dict_values([1, 3]))
>>> K | {'x': 4}
{'c', 'x', 'a'}
>>> V & {'x': 4}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for &: 'dict_values' and 'dict'
>>> V & {'x': 4}.values()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for &: 'dict_values' and 'dict_values'

在集合操作中,视图可能和其他视图、集合和字典混合。

>>> D = {'a': 1, 'b': 2, 'c': 3}
>>> D.keys() & D.keys() # 视图与视图
{'b', 'c', 'a'}
>>> D.keys() & {'b'}    # 视图与集合
{'b'}
>>> D.keys() & {'b': 1} # 视图与字典
{'b'}

如果字典项视图是可散列的,那么它们可以被视为集合。

3.X中的字典键排序

我们不能直接对视图对象进行排序,需要将其转换为列表,或者对其进行sorted()函数,返回一个新的列表。

3.X中字典大小比较不再有效

在python 3.X中不能直接用<>比较字典相对大小,但可以用==

在 3.X中has_key方法已死:in方法万岁

has_key方法已经在3.X取消,用in成员关系表达式代替。

>>> D
{'a': 1, 'b': 2, 'c': 3}
>>> D.has_key('c')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'has_key'
>>> 'c' in D
True