【Chapter 3】 Python的数据结构、函数和文件
本章讨论 Python 的内置功能,我们会从Python最基础的数据结构开始:元组、列表、字典和集合。然后会讨论创建你自己的、可重复使用的Python函数。最后,会学习Python的文件对象,以及如何与本地硬盘交互。
3.1 数据结构和序列
元组(Tuple)
元组是长度固定,不可改变的序列(可以看做长度固定,且不可变的列表)。创建元祖的方法是用逗号:
1. 合并元组
用 “+” 合并元组。
In [11]: (4,5,6)+(7,8)
Out[11]: (4, 5, 6, 7, 8)
2. 取出元组
In [14]: tup = 4, 5, (6, 7)
...: a, b, (c, d) = tup
...:
In [15]: d
Out[15]: 7
交换变量名
In [19]: a
Out[19]: 4
In [20]: b
Out[20]: 5
In [21]: b,a = a,b
In [22]: a
Out[22]: 5
In [23]: b
Out[23]: 4
类似上例的 unpacking 方法常用来迭代元组或列表序列:
In [35]: for a,b,c in seq:
...: print('a={0},b={1},c={2}'.format(a,b,c))
...:
a=1,b=2,c=3
a=4,b=5,c=6
a=7,b=8,c=9
另一种更高级的 unpacking 方法是用于只取出tuple中开头几个元素,剩下的元素直接赋给*rest
:
In [36]: values = 1,2,3,4,5
In [37]: a,b,*rest = values
In [38]: a,b
Out[38]: (1, 2)
In [39]: rest
Out[39]: [3, 4, 5]
rest 部分是你想要丢弃的,名字本身无所谓,通常用下划线来代替:
In [40]: a, b, *_ = values
In [41]: _
Out[41]: [3, 4, 5]
3. Tuple methods(元组方法)
因为tuple的大小和内容都不能改变,所以方法也很少。count
用来计算某个值出现的次数,list中也有这个方法:
In [42]: a = (1, 2, 2, 2, 3, 4, 2)
...: a.count(2)
...:
Out[42]: 4
List(列表)
列表的灵活性就很强了,大小和内容都可以变。
list 函数通常用来具现化迭代器或生成器:
In [43]: gen =range(10)
In [44]: lis(gen)
In [45]: list(gen)
Out[45]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1. 添加
list.append()
在列表末尾添加;
list.insert(index,element)
可以在特定的位置插入元素 ;
警告:与
append
相比,insert
耗费的计算量大,因为对后续元素的引用必须在内部迁移,以便为新元素提供空间。如果要在序列的头部和尾部插入元素,你可能需要使用collections.deque
,一个双尾部队列。
2.删除
insert的逆运算是pop(index)
,它移除并返回指定位置的元素;
可以用remove
去除某个值,remove('element')
会先寻找第一个值并除去;
如果不考虑性能,使用append
和remove
,可以把Python的列表当做完美的“多重集”数据结构。
3.包含
用in
可以检查列表是否包含某个值:
In [55]: 'dwarf' in b_list
Out[55]: True
4. 串联和组合列表
与元组类似,可以用加号将两个列表串联起来:
In [57]: [4, None, 'foo'] + [7, 8, (2, 3)]
Out[57]: [4, None, 'foo', 7, 8, (2, 3)]
如果已经定义了一个列表,用extend
方法可以追加多个元素:
In [58]: x = [4, None, 'foo']
In [59]: x.extend([7, 8, (2, 3)])
In [60]: x
Out[60]: [4, None, 'foo', 7, 8, (2, 3)]
通过加法将列表串联的计算量较大,因为要新建一个列表,并且要复制对象。
用extend
追加元素,尤其是到一个大列表中,更为可取。因此:
everything = []
for chunk in list_of_lists:
everything.extend(chunk)
要比串联方法快:
everything = []
for chunk in list_of_lists:
everything = everything + chunk
总结:
append 和 extend 的区别。
- append 是把元素添加到一个list里
- extend 是把两个 list (多个元素)结合在一起
extend 和 + 的区别
+
是创建了一个新的list并返回,运算量大- extend 是在原本的list上做了更改,运算量小
5. 排序
你可以用sort
函数将一个列表原地排序(不创建新的对象):
In [61]: a = [7, 2, 5, 1, 3]
In [62]: a.sort()
In [63]: a
Out[63]: [1, 2, 3, 5, 7]
sort
有一些选项,有时会很好用。其中之一是二级排序key,可以用这个key进行排序。例如,我们可以按长度对字符串进行排序:
In [64]: b = ['saw', 'small', 'He', 'foxes', 'six']
In [65]: b.sort(key=len)
In [66]: b
Out[66]: ['He', 'saw', 'six', 'small', 'foxes']
二分搜索和维持一个排好序的 list 列表
bisect
模块支持二分查找,和向已排序的列表插入值。:
bisect.bisect
其目的在于查找该数值将会插入的位置并返回,而不会插入。
bisect.insort
是插入一个值,而插入后的列表还是整齐的。
In [47]: c= [1,2,2,2,3,4,7]
In [48]: bisect.bisect(c,2)
Out[48]: 4
In [49]: bisect.bisect(c,5)
Out[49]: 6
In [50]: bisect.bisect(c,6)
Out[50]: 6
In [51]: c
Out[51]: [1, 2, 2, 2, 3, 4, 7]
注意:bisect模块不会检查list是否是排好序的,对未排序的列表使用bisect
不会产生错误,但结果不一定正确 ,所以用这个模块之前要先把list排序。
Slicing (切片)
[start:stop], 输出的结果包含开头,不包含结尾。所以输出的结果的数量是stop-start 。即区间为[start,stop)。
In [73]: seq = [7, 2, 3, 7, 5, 6, 0, 1]
In [74]: seq[1:5]
Out[74]: [2, 3, 7, 5]
start
或 stop
都可以被省略,省略之后,分别默认序列的开头和结尾(负索引表示倒数开始多少个的意思):
In [77]: seq[:5]
Out[77]: [7, 2, 3, 6, 3]
In [78]: seq[-4:]
Out[78]: [5, 6, 0, 1]
两个冒号后面的数代表步长(step),就是隔(step-1)个元素取一次:
In [80]:seq
Out[80]:[7, 2, 3, 6, 3, 5, 6, 0, 1]
In [81]: seq[::2]
Out[81]: [7, 3, 3, 6, 1]
序列函数
1. enumerate(枚举)函数
迭代一个序列时,你可能想跟踪当前项的序号 。一个比较直白的方法是:
i = 0
for value in collection:
# do something with value
i += 1
但 Python 内建了一个enumerate
函数,可以返回(i, value)
元组序列:
for i, value in enumerate(collection):
enumerate 通常用来把一个 list 中的位置和值映射到一个 dcit 字典里:
In [57]: some_list = ['foo','bar','baz']
In [58]: mapping={}
In [59]: for i,v in enumerate(some_list):
...: mapping[v] = i
...:
In [60]: mapping
Out[60]: {'bar': 1, 'baz': 2, 'foo': 0}
2. sorted()
sorted
函数可以从任意序列的元素返回一个新的排好序的列表:
In [71]: sorted([1,23,4,5,2,7])
Out[71]: [1, 2, 4, 5, 7, 23]
In [72]: sorted('jadon sunshine')
Out[72]: [' ', 'a', 'd', 'e', 'h', 'i', 'j', 'n', 'n', 'n', 'o', 's', 's', 'u']
sorted
函数可以接受和sort
相同的参数。
3. zip 函数
用于"pairs"(成对)。把多个列表、元组或其它序列每个对应的元素变成一对,最后返回一个含有 tuple 的 list:
In [73]: seq1 = ['foo', 'bar', 'baz']
In [74]: seq2 = ['one', 'two', 'three']
In [75]: zipped = zip(seq1,seq2)
In [76]: list(zipped)
Out[76]: [('foo', 'one'), ('bar', 'two'), ('baz', 'three')]
zip 可以接收任意长度的序列,最后返回的结果取决于最短的序列:
In [77]: seq3 = [False, True]
...: list(zip(seq1, seq2, seq3))
...:
Out[77]: [('foo', 'one', False), ('bar', 'two', True)]
zip 的一个常见用法是同时迭代多个序列,可以和 enumerate 搭配起来用:
In [79]: for i,(a,b) in enumerate(zip(seq1,seq2)):
...: print('{0}:{1},{2}'.format(i,a,b))
...:
0:foo,one
1:bar,two
2:baz,three
如果给我们一个压缩过的序列,我们可以将其解压:
In [81]: pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'),
...: ('Schilling', 'Curt')]
...:
In [82]: first_names,last_names = zip(*pitchers) #解压
In [84]: first_names
Out[84]: ('Nolan', 'Roger', 'Schilling')
In [85]: last_names
Out[85]: ('Ryan', 'Clemens', 'Curt')
5.reversed
reverse可以倒叙迭代序列:
In [86]: list(reversed(range(10)))
Out[86]: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
注意,revered是一个generator(生成器,之后会详细讲),所以必须需要(list 或 for循环)来具现化 。
字典
1. 字典创建、访问、插入、更改
字典也被叫做hash map 或 associative array。结构就是key-value pairs.创建方式是用{key : value}
:
In [101]: empty_dict = {}
In [102]: d1 = {'a' : 'some value', 'b' : [1, 2, 3, 4]}
In [103]: d1
Out[103]: {'a': 'some value', 'b': [1, 2, 3, 4]}
你可以像访问列表或元组中的元素一样,访问、插入或设定字典中的元素:
In [87]: d1 = {'a': 'some value', 'b': [1, 2, 3, 4]}
In [88]: d1[7] ='an integer'#插入
In [90]: d1
Out[90]: {7: 'an integer', 'a': 'some value', 'b': [1, 2, 3, 4]}
In [91]: d1['b']#访问
Out[91]: [1, 2, 3, 4]
你可以用检查列表和元组是否包含某个值得方法,检查字典中是否包含某个键:
In [93]: 'b' in d1
Out[93]: True
可以用del
关键字或pop
方法(返回值得同时删除键)删除值:
In [94]: d1[5] = 'some value'
In [95]: d1
Out[95]: {5: 'some value', 7: 'an integer', 'a': 'some value', 'b': [1, 2, 3, 4]}
In [96]: del d1[5]
In [97]: d1
Out[97]: {7: 'an integer', 'a': 'some value', 'b': [1, 2, 3, 4]}
In [101]: d1
Out[101]: {7: 'an integer', 'a': 'some value', 'b': [1, 2, 3, 4]}
In [102]: ret = d1.pop(7)
In [103]: d1
Out[103]: {'a': 'some value', 'b': [1, 2, 3, 4]}
keys 和 values 方法能返回dict中key-value组合的迭代器,不过并不按什么顺序。如果想让 keys 和 values 每次打印的顺序相同的话:
In [106]: d1
Out[106]: {7: 'integer', 'a': 'some value', 'b': [1, 2, 3, 4]}
In [108]: list(d1.keys())
Out[108]: ['a', 'b', 7]
In [110]: list(d1.values())
Out[110]: ['some value', [1, 2, 3, 4], 'integer']
用update
方法可以将一个字典与另一个融合:
In [119]: d1.update({'b' : 'foo', 'c' : 12})
In [120]: d1
Out[120]: {'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}
update
方法是原地改变字典,因此任何传递给update
的键的旧的值都会被舍弃。
2.用序列创建字典
常常,你可能想将两个序列配对组合成字典。下面是一种写法:
mapping = {}
for key, value in zip(key_list, value_list):
mapping[key] = value
因为字典本质上是 2元元组 的集合,dict可以接受 2元元组的列表:
In [121]: mapping = dict(zip(range(5), reversed(range(5))))
In [122]: mapping
Out[122]: {0: 4, 1: 3, 2: 2, 3: 1, 4: 0}
3. Default value(默认值)
如果 dict 中某个key存在的话,就返回该value,否则的话,就返回一个默认值:
if key in some_dict:
value = some_dict[key]
else:
value = default_value
不过dict中的get 和pop方法能设置默认值,即能把上面的代码简写为:
value = some_dict.get(key, default_value)
如果key不存在的话,get方法默认会返回None,而pop则会引发一个错误。
通过设定值,一个常用的场景是一个dict中的value也是其他集合,比如list。举例说明,我们想要把一些单词按首字母归类:
In [128]: words = ['apple', 'bat', 'bar', 'atom', 'book']
...: by_letter = {}
...:
In [129]: for word in words:
...: letter = word[0]
...: if letter not in by_letter:
...: by_letter[letter] = [word]#新建一个 key:value
...: else :
...: by_letter[letter].append(word)#如果已经存在这个 key 直接添加 value
...:
In [129]:
In [130]: by_letter
Out[130]: {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}
而 setdefault 方法则是专门为这个用途存在的,上面的循环可以写为:
for word in words:
letter = word[0]
by_letter.setdefault(letter, []).append(word)
使用setdefault() 初始化字典键值. 使用字典的时候经常会遇到这样一种应用场景:动态更新字典,像如上面代码,如果 key 不在 dictionary 中那么就添加它并把它对应的值初始为空列表[] ,然后把元素append到空列表中。
内建的collections模块有一个有用的class,defaultdict,这个能让上述过程更简单。创建方法是传递一个type或是函数:
from collections import defaultdict
by_letter = defaultdict(list)
for word in words:
by_letter[word[0]].append(word)
Valid dict key types(有效的key类型)
通常key的类型是不可更改的常量类型(int,float,string)或tuple。专业的叫法是hashability。可以查看一个object是否是hashable,只要是hashable的,就可以当做dict中的key。这里用hash函数查看:
In [127]: hash('string')
Out[127]: 5023931463650008331
In [128]: hash((1, 2, (2, 3)))
Out[128]: 1097636502276347782
In [129]: hash((1, 2, [2, 3])) # fails because lists are mutable
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 hash((1, 2, [2, 3])) # fails because lists are mutable
TypeError: unhashable type: 'list'
要用列表当做键,一种方法是将列表转化为元组,只要内部元素可以被哈希,它也就可以被哈希:
In [131]: d = {}
In [132]: d[tuple([1,2,3])]=5
In [133]: d
Out[133]: {(1, 2, 3): 5}
Set 集合
集合是无序的不可重复的元素的集合。你可以把它当做字典,但是只有键没有值。可以用两种方式创建集合:通过 se t函数或使用尖括号 set 语句:
In [133]: set([2, 2, 2, 1, 3, 3])
Out[133]: {1, 2, 3}
In [134]: {2, 2, 2, 1, 3, 3}
Out[134]: {1, 2, 3}
集合支持合并、交集、差分和对称差等数学集合运算。考虑两个示例集合:
In [135]: a = {1, 2, 3, 4, 5}
In [136]: b = {3, 4, 5, 6, 7, 8}
合并是取两个集合中不重复的元素。可以用union
方法,或者|
运算符:
In [136]: # 并集
...: a.union(b)
Out[136]: {1, 2, 3, 4, 5, 6, 7, 8}
In [137]: a|b
Out[137]: {1, 2, 3, 4, 5, 6, 7, 8}
交集的元素包含在两个集合中。可以用intersection
或&
运算符:
In [138]: # 交集
...: a.intersection(b)
Out[138]: {3, 4, 5}
In [139]: a & b
Out[139]: {3, 4, 5}
所有逻辑集合操作都有另外原地实现方法,它可以直接用结果替代集合的内容。对于大的集合,这么做效率更高:
In [141]: c = a.copy()
In [142]: c |= b
In [143]: c
Out[143]: {1, 2, 3, 4, 5, 6, 7, 8}
In [144]: d = a.copy()
In [145]: d &= b
In [146]: d
Out[146]: {3, 4, 5}
set 的元素必须是不可更改的。如果想要 list 一样的元素,只能变为tuple:
In [157]: my_data = [1,2,3,4]
In [158]: my_set = {tuple(my_data)}
In [159]: my_set
Out[159]: {(1, 2, 3, 4)}
你还可以检测一个集合是否是另一个集合的子集或父集:
In [150]: a_set = {1, 2, 3, 4, 5}
In [151]: {1, 2, 3}.issubset(a_set)
Out[151]: True
In [152]: a_set.issuperset({1, 2, 3})
Out[152]: True
集合的内容相同时,集合才对等:
In [153]: {1, 2, 3} == {3, 2, 1}
Out[153]: True
列表、集合和字典推导式
list comprehension(列表推导式)是python里最受喜爱的一个特色。我们能简洁地构造一个list:
[expr for val in collection if condiction]
相当于:
result = []
for val in collection:
if condition:
result.append(expr)
比如,给定一个list,里面有很多string,我们只要留下string长度超过2的,并将其转换为大写:
In [160]: strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
In [161]: [x.upper() for x in strings if len(x) > 2]
Out[161]: ['BAT', 'CAR', 'DOVE', 'PYTHON']
dict推导式:
dict_comp = {key-expr: value-expr for value in collection if condition}
set的推导式:(集合的推导式与列表很像,只不过用的是尖括号: )
set_comp = {expr for value in collection if condition}
基于上面的例子,我们想要一个集合来保存string的长度:
map
函数可以进一步简化:
In [158]: set(map(len, strings))
Out[158]: {1, 2, 3, 4, 6}
下面一个简单的字典表达式例子,查找 字符串和其在list中对应的index:
In [159]: loc_mapping = {val : index for index, val in enumerate(strings)}
In [160]: loc_mapping
Out[160]: {'a': 0, 'as': 1, 'bat': 2, 'car': 3, 'dove': 4, 'python': 5}
作者:SeanCheney
链接:https://www.jianshu.com/p/b444cda10aa0
來源:
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Nested list comprehensions(嵌套列表表达式)
假设我们有一个list,list中又有不同的list表示英语和西班牙语的姓名:
all_data = [['John', 'Emily', 'Michael', 'Mary', 'Steven'],
['Maria', 'Juan', 'Javier', 'Natalia', 'Pilar']]
你可能是从一些文件得到的这些名字,然后想按照语言进行分类。现在假设我们想用一个列表包含所有的名字,这些名字中包含两个或更多的e。可以用for循环来做:
names_of_interest = []
for names in all_data:
enough_es = [name for name in names if name.count('e') >= 2]
names_of_interest.extend(enough_es)
可以用嵌套列表推导式的方法,将这些写在一起,如下所示:
In [169]: result = [name for names in all_data for name in names if name.count('e')>=2]
In [170]: result
Out[170]: ['Steven']
for部分是根据嵌套的顺序来写的,从外层的loop到内层的loop。这里一个例子是把tuple扁平化成一个整数列表:
In [171]: some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
...: flattened = [x for tup in some_tuples for x in tup]
...: flattened
...:
Out[171]: [1, 2, 3, 4, 5, 6, 7, 8, 9]
一定要记住顺序是和我们写for loop一样的:
In [172]: flatteded = []
...:
...: for tup in some_tuples:
...: for x in tup:
...: flattened.append(x)
列表表达式里再有一个列表表达式也是可以的,可以生成a list of lists:
In [173]: [[x for x in tup] for tup in some_tuples]
Out[173]: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]