5. 列表
在博客文章Python快速入门里,我们已经学习和了解了Python的各种数据结构,接下来我们继续学习和深挖一些与列表、元组和字典有关的东西。
5.1 更多的列表特性
列表数据类型还有很多的方法。这里是列表对象方法的清单:
list.append(x)
在列表的末尾添加一个元素。相当于 a[len(a):] = [x] 。
list.extend(iterable)
使用可迭代对象中的所有元素来扩展列表。相当于 a[len(a):] = iterable 。
list.insert(i, x)
在给定的位置插入一个元素。第一个参数是要插入的元素的索引,所以 a.insert(0, x) 插入在列表头部, a.insert(len(a), x) 等同于 a.append(x) 。
list.remove(x)
移除列表中第一个值为 x 的元素。如果没有这样的元素,则抛出 ValueError 异常。
list.pop([i])
删除列表中给定位置的元素并返回它。如果没有给定位置,a.pop() 将会删除并返回列表中的最后一个元素。( 方法签名中 i 两边的方括号表示这个参数是可选的,而不是要你输入方括号。你会在 Python 参考库中经常看到这种表示方法)。
list.clear()
删除列表中所有的元素。相当于 del a[:] 。
list.index(x[, start[, end]])
返回列表中第一个值为 x 的元素的从零开始的索引。如果没有这样的元素将会抛出 ValueError 异常。
可选参数 start 和 end 是切片符号,用于将搜索限制为列表的特定子序列。返回的索引是相对于整个序列的开始计算的,而不是 start 参数。
list.count(x)
返回元素 x 在列表中出现的次数。
list.sort(key=None, reverse=False)
对列表中的元素进行排序(参数可用于自定义排序,解释请参见 sorted())。
list.reverse()
反转列表中的元素。
list.copy()
返回列表的一个浅拷贝。相当于 a[:] 。
列表方法示例:
>>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
>>> fruits.count('apple')
2
>>> fruits.count('tangerine')
0
>>> fruits.index('banana')
3
>>> fruits.index('banana', 4) # Find next banana starting a position 4
6
>>> fruits.reverse()
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
>>> fruits.append('grape')
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
>>> fruits.sort()
>>> fruits
['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
>>> fruits.pop()
'pear'
你可能已经注意到,像 insert ,remove 或者 sort 方法,只修改列表,没有打印出返回值——它们返回默认值 None 。
5.2 列表推导式
列表推导式提供了一个更简单的创建列表的方法。常见的用法是把某种操作应用于序列或可迭代对象的每个元素上,然后使用其结果来创建列表,或者通过满足某些特定条件元素来创建子序列。
下面是比较经典的创建列表方法:
my_list = []
for i in range(1, 11):
my_list.append(i)
我们使用了 my_list.append(i) 来赋值列表的元素。
或者
my_list = []
for i in range(1, 11):
my_list += [ i ]
我们可以使用了 my_list += [ i ] 来赋值列表的元素。
其实有更简洁的方法来赋值给列表元素,创建列表:
my_list = [ i for i in range(1, 11) ]
5.3 列表的求和
列表内的元素如何求和呢?经典的办法就是各个元素累加,最后total就是所有元素的和,但Python内置的函数sum()支持列表求和。
my_list = [ i for i in range(1, 11) ]
total = 0
for i in my_list:
total += i
print(total)
my_list = [ i for i in range(1, 11) ]
print(sum(my_list))
5.4 求整数的各位数字的列表
方法一:用number % 10,得到余数就是位数上的数字,然后number整数10,再计算余数,最后计算出所有的余数,就是number从低位到高位各个位上数字,所以最后digits[::-1]就是取反相的列表。
number = 291201212
digits = []
while number > 0:
digits += [ number % 10]
number //= 10 # number = number // 10
print(digits[::-1])
方法二:先把整数number通过str()转化为字符串,然后枚举字符串的每个字符,然后用int()把字符转为整数,最后所有的数字都放在列表里:
number = 291201212
digits = [ int(digit_c) for digit_c in list(str(number)) ]
print(digits)
# 展开代码后就是下面的几行:
digits = []
digits_s = list(str(number))
for digits_c in digits_s:
digits += [ int(digits_c) ]
print(digits)
方法三:增加使用了函数map(),把list(str(number))的结果,转化为整数,然后再把结果转化为列表:
number = 291201212
# list(str(number)): ['2', '9', '1', '2', '0', '1', '2', '1', '2']
digits = list(map(int, list(str(number))))
print(digits)
#digits: [2, 9, 1, 2, 0, 1, 2, 1, 2]
刚开始肯定不习惯的,慢慢琢磨,使用多了,就习惯了。不过,我建议要在代码易读和简洁之间平衡一下,一般不要过度嵌套太多函数,三到四层就可以了。例如:这就是四层嵌套:digits = list(map(int, list(str(number))))
5.5 整数的列表的综合使用
下面是一个函数createPrimeSieve(),用来通过埃拉托斯特尼筛法求小于n的所有质数,在解决Project Euler Problem时经常会使用的,具体算法可以到网上搜索学习。
def createPrimeSieve(n):
""" create prime sieve """
sieve = [True] * n
sieve[0] = sieve[1] = False
for i in range(2, int(n**0.5)+1):
if True == sieve[i]:
for j in range(i*i, n, i):
sieve[j] = False
return [i for i in range(1, n) if sieve[i]]
在函数中,我们首先定义了一个列表sieve ,并把其n个元素都初始化为True。
初始化第1个(sieve[0]) 和第2个(sieve[1])元素后,通过埃拉托斯特尼筛法去除合数(就是倍数 的 j)。
最后,把筛子sieve[i]中所有值为True的元素索引放到质数列表里,返回小于n的质数的列表。
5.6 嵌套的列表推导式
列表推导式中的初始表达式可以是任何表达式,包括另一个列表推导式。
考虑下面这个 4x4的矩阵,它由4个长度为4的列表组成:
matrix_list = [ [ (1+j)+4*i for j in range(4) ] for i in range(4) ]
#等价于下面几行:
matrix_list = []
for i in range(4):
matrix_list_row = []
for j in range(4):
matrix_list_row.append((1+j)+4*i)
matrix_list.append(matrix_list_row)
#打印列表
print(matrix_list)
# [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]
# 按索引值访问一个元素: matrix_list[i][j]
for i in range(4):
for j in range(4):
print('%2d' % matrix_list[i][j], end=' ')
print()
# 1 2 3 4
# 5 6 7 8
# 9 10 11 12
#13 14 15 16
6 其他数据结构(元组,集合和字典)
6.1 元组
我们看到列表和字符串有很多共同特性,例如索引和切片操作。他们是 序列 数据类型(list, tuple, range)中的两种。随着 Python 语言的发展,其他的序列类型也会被加入其中。这里介绍另一种标准序列类型: 元组。
一个元组由几个被逗号隔开的值组成,例如:
>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
>>> # Tuples are immutable:
... t[0] = 88888
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> # but they can contain mutable objects:
... v = ([1, 2, 3], [3, 2, 1])
>>> v
([1, 2, 3], [3, 2, 1])
如你所见,元组在输出时总是被圆括号包围的,以便正确表示嵌套元组。输入时圆括号可有可无,不过经常会是必须的(如果这个元组是一个更大的表达式的一部分)。给元组中的一个单独的元素赋值是不允许的,当然你可以创建包含可变对象的元组,例如列表。
虽然元组可能看起来与列表很像,但它们通常是在不同的场景被使用,并且有着不同的用途。元组是 immutable (不可变的),其序列通常包含不同种类的元素,并且通过解包(这一节下面会解释)或者索引来访问(如果是 namedtuples 的话甚至还可以通过属性访问)。列表是 mutable (可变的),并且列表中的元素一般是同种类型的,并且通过迭代访问。
6.2 集合
Python也包含有 集合 类型。集合是由不重复元素组成的无序的集。它的基本用法包括成员检测和消除重复元素。集合对象也支持像 联合,交集,差集,对称差分等数学运算。
花括号或 set() 函数可以用来创建集合。注意:要创建一个空集合你只能用 set() 而不能用 {},因为后者是创建一个空字典,这种数据结构我们会在下一节进行讨论。
以下是一些简单的示例:
>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket) # show that duplicates have been removed
{'orange', 'banana', 'pear', 'apple'}
>>> 'orange' in basket # fast membership testing
True
>>> 'crabgrass' in basket
False
>>> # Demonstrate set operations on unique letters from two words
...
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a # unique letters in a
{'a', 'r', 'b', 'c', 'd'}
>>> a - b # letters in a but not in b
{'r', 'd', 'b'}
>>> a | b # letters in a or b or both
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b # letters in both a and b
{'a', 'c'}
>>> a ^ b # letters in a or b but not both
{'r', 'd', 'b', 'm', 'z', 'l'}
类似于 列表推导式,集合也支持推导式形式
>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'r', 'd'}
6.3 字典
另一个非常有用的 Python 內置数据类型是 字典 (dict)。字典在其他语言里可能会被叫做 联合内存 或 联合数组。与以连续整数为索引的序列不同,字典是以 关键字 为索引的,关键字可以是任意不可变类型,通常是字符串或数字。如果一个元组只包含字符串、数字或元组,那么这个元组也可以用作关键字。但如果元组直接或间接地包含了可变对象,那么它就不能用作关键字。列表不能用作关键字,因为列表可以通过索引、切片或 append() 和 extend() 之类的方法来改变。
理解字典的最好方式,就是将它看做是一个 键: 值 对的集合,键必须是唯一的(在一个字典中)。一对花括号可以创建一个空字典:{} 。另一种初始化字典的方式是在一对花括号里放置一些以逗号分隔的键值对,而这也是字典输出的方式。
字典主要的操作是使用关键字存储和解析值。也可以用 del 来删除一个键值对。如果你使用了一个已经存在的关键字来存储值,那么之前与这个关键字关联的值就会被遗忘。用一个不存在的键来取值则会报错。
对一个字典执行 list(d) 将返回包含该字典中所有键的列表,按插入次序排列 (如需其他排序,则要使用 sorted(d))。要检查字典中是否存在一个特定键,可使用 in 关键字。
以下是使用字典的一些简单示例
>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'jack': 4098, 'sape': 4139, 'guido': 4127}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'jack': 4098, 'guido': 4127, 'irv': 4127}
>>> list(tel)
['jack', 'guido', 'irv']
>>> sorted(tel)
['guido', 'irv', 'jack']
>>> 'guido' in tel
True
>>> 'jack' not in tel
False
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}
当关键字是简单字符串时,有时直接通过关键字参数来指定键值对更方便
>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'guido': 4127, 'jack': 4098}
6.4 循环的技巧
当在字典中循环时,用 items() 方法可将关键字和对应的值同时取出
>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.items():
... print(k, v)
...
gallahad the pure
robin the brave
当在序列中循环时,用 enumerate() 函数可以将索引位置和其对应的值同时取出
>>> for i, v in enumerate(['tic', 'tac', 'toe']):
... print(i, v)
...
0 tic
1 tac
2 toe
当同时在两个或更多序列中循环时,可以用 zip() 函数将其内元素一一匹配。
>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
... print('What is your {0}? It is {1}.'.format(q, a))
...
What is your name? It is lancelot.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.
当逆向循环一个序列时,先正向定位序列,然后调用 reversed() 函数
>>> for i in reversed(range(1, 10, 2)):
... print(i)
...
9
7
5
3
1
如果要按某个指定顺序循环一个序列,可以用 sorted() 函数,它可以在不改动原序列的基础上返回一个新的排好序的序列
>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
... print(f)
...
apple
banana
orange
pear
6.5 序列和其它类型的比较
序列对象可以与相同类型的其他对象比较。它们使用 字典顺序 进行比较:首先比较两个序列的第一个元素,如果不同,那么这就决定了比较操作的结果。如果它们相同,就再比较每个序列的第二个元素,以此类推,直到有一个序列被耗尽。如果要比较的两个元素本身就是相同类型的序列,那么就递归进行字典顺序比较。如果两个序列中所有的元素都相等,那么我们认为这两个序列相等。如果一个序列是另一个序列的初始子序列,那么短序列就小于另一个。字典顺序对字符串来说,是使用单字符的 Unicode 码的顺序。下面是同类型序列之间比较的例子
(1, 2, 3) < (1, 2, 4)
[1, 2, 3] < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4) < (1, 2, 4)
(1, 2) < (1, 2, -1)
(1, 2, 3) == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)
注意对不同类型对象来说,只要待比较对象提供了合适的比较方法,就可以使用 < 和 > 来比较。例如,混合数值类型是通过他们的数值进行比较的,所以 0 等于 0.0,等等。否则,解释器将抛出一个 TypeError 异常,而不是随便给出一个结果。