参考:
数据结构
列表详解
list.append(x)
相当于a[len(a):] = [x]
,注意:a[len(a)]=[x](少了冒号),a[len(a):]=x(x没有方括号)都是错误的写法。
list.extend(iterable)
TODO:
list.insert(i, x)
list.remove(x)
list.pop([i])
如果不带i,则删除并返回列表的最后一个元素
list.clear()
list.index(x[, start[, end]])
返回列表中第一个值为 x 的元素的索引。这个写法太复杂,实际上就是有以下几种调用方式:
- list.index(x)
- list.index(x, start)
- list.index(x, start, end)
start, end用于将搜索限制为列表的特定子序列,但索引值始终以原始序列为起点。
list.count(x)
list.sort(*, key=None, reverse=False)
*是Python教程学习 (2)(写文章-CSDN创作中心)中讲到的特殊参数,用于表明其后的参数必须是仅限关键字参数。也就是说,后面的参数在传递实参的时候,必须给出形参名。当然,形参定义了默认参数的话,也可以不传递参数。因此,sort()可以不带参数调用。
>>> a = [1, 2, 3, 4, 5, 6]
>>> a.reverse()
>>> a
[6, 5, 4, 3, 2, 1]
>>> a.sort()
>>> a
[1, 2, 3, 4, 5, 6]
还有,不是所有数据都可以排序或比较。例如,[None, 'hello', 10]
就不可排序,因为整数不能与字符串对比,而 None 不能与其他类型对比。list.reverse()
list.copy()
注意,这个copy是浅拷贝
用列表实现堆栈
append()和pop()函数可以实现堆栈的功能
用列表实现队列
列表也可以实现队列,但是列表作为队列的效率很低。因为,在列表末尾添加和删除元素非常快,但在列表开头插入或移除元素却很慢(因为所有其他元素都必须移动一位)。
实现队列最好用collections.deque。这里用到了from...import...语句。
>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")
>>> queue.append("Graham")
>>> queue.popleft()
'Eric'
>>> queue.popleft()
'John'
>>> queue
deque(['Michael', 'Terry', 'Graham'])
列表推导式
看这个列表的创建方式
>>> squares = [x**2 for x in range(10)]
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
列表推导式的方括号内包含以下内容:一个表达式,后面为一个 for
子句,然后,是零个或多个 for
或 if
子句。结果是由表达式依据 for
和 if
子句求值计算而得出一个新列表。再例如:
>>> [(x, y) for x in range(1, 4) for y in [3, 1, 4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
在推导式中,for语句是从左向右解析的,例如:
>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
上例中的"for elem in vec for num in elem"相当于两层for循环,因此是取遍二维数组的每一个元素:
>>> for elem in vec:
... for num in elem:
嵌套的列表推导式
下面这个例子使用了推导式嵌套, 注意比较下面这个例子和前面一个例子
>>> matrix = [
... [1, 2, 3, 4],
... [5, 6, 7, 8],
... [9, 10, 11, 12],
... ]
>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
>>> # Similar to this
>>> transposed = []
>>> for i in range(4):
... transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
为什么这个例子第二个for循环相当于外循环,而前面一个例子的第二个for循环相当于内循环?原因在于第二个例子使用了嵌套推导式。如果去掉了表示推导式嵌套的方括号,结果如下:
>>> [row[i] for row in matrix for i in range(4)]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
于是得出:如果没有嵌套,后面的for循环作为内层循环;如果有嵌套,内层嵌套作为内层循环。
下面的例子中,用内置函数替代复杂的流程语句。关于zip()函数,这里先简单提一下:"
更正式的说法: zip() 返回元组的迭代器,其中第 i 个元组包含的是每个参数迭代器的第 i 个元素。
不妨换一种方式认识 zip() :它会把行变成列,把列变成行。这类似于 矩阵转置 。"
>>> list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
另外,*matrix是解包实参列表,有点像c语言的取内容,但其实不太一样。这里可以理解为*matrix得到的是三个列表[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]。但这仅仅是一种理解,*matrix本身不能单独存在。
del语句
del语句可以看作更加强大的删除list元素的功能。del是按照索引删除的,可以删除一个元素,也可以删除一个切片。因此,del a[:]相当于clear()。
另外,del a是删除整个变量,之后如果没有再次给a赋值,则使用a会报错。
元组和序列
关于元组(tuple),注意以下几个特殊之处:
>>> v = ([1, 2, 3], [3, 2, 1])
>>> v[0][1]=4
>>> v
([1, 4, 3], [3, 2, 1])
- 元组可包含异质元素序列,而列表元素一般为同质类型,可迭代访问。比如整数和字符串就是异质的
- 用一对空圆括号就可以创建空元组;只有一个元素的元组可以通过在这个元素后添加逗号来构建
>>> empty = ()
>>> singleton = 'hello', # <-- note trailing comma
>>> len(empty)
0
>>> singleton
('hello',)
集合
集合是由不重复元素组成的无序容器。
创建集合用花括号或 set() 函数。注意,创建空集合只能用 set()
,不能用 {}
,{}
创建的是空字典,下一小节介绍数据结构:字典。
支持合集、交集、差集、对称差分等数学运算。
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a
{'c', 'r', 'b', 'd', 'a'}
>>> b
{'l', 'm', 'c', 'z', 'a'}
>>> a - b
{'d', 'r', 'b'}
>>> a | b
{'l', 'm', 'c', 'r', 'b', 'd', 'z', 'a'}
>>> a & b
{'c', 'a'}
>>> a ^ b # letters in a or b but not both
{'z', 'l', 'm', 'd', 'r', 'b'}
和列表推导式类似,集合也支持推导式。
字典
字典有点像C++ STL的map。字典在其他语言中可能会被称为“关联存储”或“关联数组”。字典的key可以是任何不可变类型。如果元组没有包含不可变的元素(如列表),则元组也可以作为key。反之,如果一个元组直接或间接地包含了任何可变对象,则不能作为键。
花括号 {}
用于创建空字典。另一种初始化字典的方式是,在花括号里输入逗号分隔的键值对,这也是字典的输出方式。
用 del
可以删除键值对。对字典执行 list(d)
操作,返回该字典中所有键的列表,按插入次序排列(如需排序,请使用 sorted(d)
)。检查字典里是否存在某个键,使用关键字 in。
dict() 构造函数可以直接用键值对序列创建字典。
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'guido': 4127, 'jack': 4098}
字典推导式可以用任意键值表达式创建字典。
>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
但奇怪的是,下面的表达式是有问题的:
>>> {x: y for x in range(6) for y in range(1, 7)}
{0: 6, 1: 6, 2: 6, 3: 6, 4: 6, 5: 6}
>>> {x: y for x in range(6) for y in [2, 3, 4, 5, 6, 7]}
{0: 7, 1: 7, 2: 7, 3: 7, 4: 7, 5: 7}
关键字是比较简单的字符串时,直接用关键字参数指定键值对更便捷:
>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'guido': 4127, 'jack': 4098}
循环的技巧
- 当对字典执行循环时,可以使用 items() 方法同时提取键及其对应的值
- 在序列中循环时,用 enumerate() 函数可以同时取出位置索引和对应的值
- 同时循环两个或多个序列时,用 zip() 函数可以将其内的元素一一匹配
- 为了逆向对序列进行循环,可以求出欲循环的正向序列,然后调用 reversed() 函数
- 按指定顺序循环序列,可以用 sorted() 函数,在不改动原序列的基础上,返回一个重新的序列
- 使用 set() 去除序列中的重复元素。使用 sorted() 加 set() 则按排序后的顺序,循环遍历序列中的唯一元素
- 一般来说,在循环中修改列表的内容时,创建新列表比较简单,且安全。我理解这个说法针对的是类似下面的操作
>>> import math
>>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
>>> for value in raw_data:
... if math.isnan(value):
... raw_data.remove(value)
...
>>> raw_data
[56.2, 51.7, 55.3, 52.5, 47.8]
上面的例子中,直接查找并删除raw_data这个列表中的NaN,虽然结果是对的,但这种做法是不推荐的。正确的操作见原文中的例子。
深入条件控制
比较运算符 in
和 not in
用于执行确定一个值是否存在(或不存在)于某个容器中的成员检测。 运算符 is
和 is not
用于比较两个对象是否是同一个对象。 所有比较运算符的优先级都一样,且低于任何数值运算符。
比较操作支持链式操作。例如,a < b == c
校验 a
是否小于 b
,且 b
是否等于 c
。
比较操作可以用布尔运算符 and
和 or
组合,并且,比较操作(或其他布尔运算)的结果都可以用 not
取反。
布尔运算符 and
和 or
是所谓的 短路 运算符,这点和c语言一样。用作普通值而不是布尔值时,短路运算符的返回值通常是最后一个求了值的参数;这点和c语言是不一样的,c的布尔表达式返回的一定是true或false。
>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim'
上面的例子中对两个字符串执行or操作的定义尚不清楚,TODO
注意,Python 与 C 不同,在表达式内部赋值必须显式使用 海象运算符 :=
。 这避免了 C 程序中常见的问题:要在表达式中写 ==
时,却写成了 =
。
序列和其他类型的比较
序列对象可以与相同序列类型的其他对象比较。这种比较使用 字典式 顺序。
当比较不同类型的对象时,只要待比较的对象提供了合适的比较方法,就可以使用 <
和 >
进行比较。