第三章 Python的数据结构、函数和文件
1. 数据结构和序列
元组
元组是一个固定长度,不可改变的Python序列对象。创建元组的最简单方式,是用逗号分隔一列值:
In [1]: tup = 4, 5, 6
In [2]: tup
Out[2]: (4, 5, 6)
当用复杂的表达式定义元组,最好将值放到圆括号内,如下所示:
In [3]: nested_tup = (4, 5, 6), (7, 8)
In [4]: nested_tup
Out[4]: ((4, 5, 6), (7, 8))
可以用方括号访问元组中的元素。
In [6]: tup = tuple('string')
In [7]: tup
Out[7]: ('s', 't', 'r', 'i', 'n', 'g')
In [8]: tup[0]
Out[8]: 's'
一旦创建了元组,元组中的对象就不能修改
如果元组中的某个对象是可变的,比如列表,可以在原位进行修改:
In [11]: tup[1].append(3)
In [12]: tup
Out[12]: ('foo', [1, 2, 3], True)
可以用加号运算符将元组串联起来
In [13]: (4, None, 'foo') + (6, 0) + ('bar',)
Out[13]: (4, None, 'foo', 6, 0, 'bar')
tuple计数方法
In [34]: a = (1, 2, 2, 2, 3, 4, 2)
In [35]: a.count(2)
Out[35]: 4
列表
与元组对比,列表的长度可变、内容可以被修改。你可以用方括号定义或用list函数:
In [36]: a_list = [2, 3, 7, None]
In [37]: tup = ('foo', 'bar', 'baz')
In [38]: b_list = list(tup)
In [39]: b_list
Out[39]: ['foo', 'bar', 'baz']
In [40]: b_list[1] = 'peekaboo'
In [41]: b_list
Out[41]: ['foo', 'peekaboo', 'baz']
添加和删除元素
可以用append在列表末尾添加元素:
In [45]: b_list.append('dwarf')
In [46]: b_list
Out[46]: ['foo', 'peekaboo', 'baz', 'dwarf']
insert可以在特定的位置插入元素:
In [47]: b_list.insert(1, 'red')
In [48]: b_list
Out[48]: ['foo', 'red', 'peekaboo', 'baz', 'dwarf']
insert的逆运算是pop,它移除并返回指定位置的元素:
In [49]: b_list.pop(2)
Out[49]: 'peekaboo'
In [50]: b_list
Out[50]: ['foo', 'red', 'baz', 'dwarf']
可以用remove去除某个值,remove会先寻找第一个值并除去:
In [51]: b_list.append('foo')
In [52]: b_list
Out[52]: ['foo', 'red', 'baz', 'dwarf', 'foo']
In [53]: b_list.remove('foo')
In [54]: b_list
Out[54]: ['red', 'baz', 'dwarf', 'foo']
用in可以检查列表是否包含某个值:
In [55]: 'dwarf' in b_list
Out[55]: True
否定in可以再加一个not:
In [56]: 'dwarf' not in b_list
Out[56]: False
串联和组合列表
与元组类似,可以用加号将两个列表串联起来:
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)]
排序
可以用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']
切片
用切边可以选取大多数序列类型的一部分,切片的基本形式是在方括号使用(不包含结束元素)[start:stop:step]
In [73]: seq = [7, 2, 3, 7, 5, 6, 0, 1]
In [74]: seq[1:5]
Out[74]: [2, 3, 7, 5]
序列函数
enumerate函数
python内建了一个enumerate函数,可以返回(i, value)元组序列:
In [83]: some_list = ['foo', 'bar', 'baz']
In [84]: mapping = {}
In [85]: for i, v in enumerate(some_list):
....: mapping[v] = i
In [86]: mapping
Out[86]: {'bar': 1, 'baz': 2, 'foo': 0}
zip函数
zip可以将多个列表、元组或其它序列成对组合成一个元组列表(长度取决于最短的序列):
In [89]: seq1 = ['foo', 'bar', 'baz']
In [90]: seq2 = ['one', 'two', 'three']
In [91]: zipped = zip(seq1, seq2)
In [92]: list(zipped)
Out[92]: [('foo', 'one'), ('bar', 'two'), ('baz', 'three')]
能结合enumerate使用:
n [95]: 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
字典
字典可能是Python最为重要的数据结构。创建字典的方法之一是使用尖括号,用冒号分隔键和值:
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]}
keys和values是字典的键和值的迭代器方法。虽然键值对没有顺序,这个方法可以用相同的顺序输出键和值:
keys和values是字典的键和值的迭代器方法。虽然键值对没有顺序,这两个方法
可以用相同的顺序输出键和值:
用update方法可以将一个字典与另一个融合
update方法是原地改变字典,因此任何传递给update的键的旧的值都会被舍弃:
In [119]: d1.update({'b' : 'foo', 'c' : 12})
In [120]: d1
Out[120]: {'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}
有效的键类型
字典的值可以是任意Python对象,而键通常是不可变的标量类型(整数浮点型、字符串)或元组(元组中的对象必须是不可变的)
集合
集合是无序的不可重复的元素的集合。可以用两种方式创建集合:通过set函数或使用尖括号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}
集合支持合并、交集、差分和对称差等数学集合运算。
列表、集合和字典推导式
列表推导式
[expr for val in collection if condition]
等同于
result = []
for val in collection:
if condition:
result.append(expr)
集合的推导式
set_comp = {expr for value in collection if condition}
字典的推导式
dict_comp = {key-expr : value-expr for value in collection if condition}
2 函数
函数使用def关键字声明,用return关键字返回值:
def my_function(x, y, z=1.5):
if z > 1:
return z * (x + y)
else:
return z / (x + y)
函数可以有一些位置参数(positional)和一些关键字参数(keyword)关键字参数通常用于指定默认值或可选参数。在上面的函数中,x和y是位置参数,而z则是关键字参数。也就是说,该函数可以下面这两种方式进调用:
(位置参数必填,关键字参数选填,可设置默认值)
my_function(5, 6, z=0.7)
my_function(3.14, 7, 3.5)
my_function(10, 20)
命名空间、作用域,和局部函数
任何在函数中赋值的变量默认都是被分配到局部命名空间中的。局部命名空间是在函数被调用时创建的,函数参数会立即填入该命名空间。在函数执行完毕之后,局部命名空间就会被销毁(会有一些例外的情况,具体请参见后面介绍闭包的那一节)。
返回多个值
def f():
a = 5
b = 6
c = 7
return a, b, c
a, b, c = f()
匿名(lambda)函数
Python支持一种被称为匿名的、或lambda函数。它仅由单条语句组成,语句的结果就是返回值。它是通过lambda关键字定义的,这个关键字没有别的含义,仅仅是说“我们正在声明的是一个匿名函数”。
def short_function(x):
return x * 2
equiv_anon = lambda x: x * 2
生成器
能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)是Python的一个重要特点。这是通过一种叫做迭代器协议(iterator protocol,它是一种使对象可迭代的通用方式)的方式实现的,一个原生的使对象可迭代的方法。比如说,对字典进行迭代可以得到其所有的键。
生成器(generator)是构造新的可迭代对象的一种简单方式。一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列即每返回一个值之后暂停,直到下一个值被请求时再继续。要创建一个生成器,只需将函数中的return替换为yeild即可:
def squares(n=10):
print('Generating squares from 1 to {0}'.format(n ** 2))
for i in range(1, n + 1):
yield i ** 2
调用该生成器时,没有任何代码会被立即执行,直到你从该生成器中请求元素时,它才会开始执行其代码
生成器表达式
In [189]: gen = (x ** 2 for x in range(100))
In [190]: gen
Out[190]: <generator object <genexpr> at 0x7fbbd5ab29e8
等于
def _make_gen():
for x in range(100):
yield x ** 2
gen = _make_gen()
itertools模块
标准库itertools模块中有一组用于许多常见数据算法的生成器。例如,groupby可以接受任何序列和一个函数。它根据函数的返回值对序列中的连续元素进行分组。下面是一个例子:
In [193]: import itertools
In [194]: first_letter = lambda x: x[0]
In [195]: names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']
In [196]: for letter, names in itertools.groupby(names,
first_letter):
.....: print(letter, list(names)) # names is a generator
A ['Alan', 'Adam']
W ['Wes', 'Will']
A ['Albert']
S ['Steven']
错误和异常处理
想优雅地处理错误,让它返回输入值。我们可以写一个函数,在try/except中调用,当float(x)抛出异常时,才会执行except的部分:
def attempt_float(x):
try:
return float(x)
except:
return x
可以用元组包含多个异常:
def attempt_float(x):
try:
return float(x)
except (TypeError, ValueError):
return x
你想无论try部分的代码是否成功,都执行一段代码。可以使用finally,你可以用else让只在try部分成功的情况下,才执行代码:
f = open(path, 'w')
try:
write_to_file(f)
except:
print('Failed')
else:
print('Succeeded')
finally:
f.close()
3 文件和操作系统
为了打开一个文件以便读写,可以使用内置的open函数以及一个相对或绝对的文件路径:
In [207]: path = 'examples/segismundo.txt'
In [208]: f = open(path)
用with语句可以可以更容易地清理打开的文件这样可以在退出代码块时,自动关闭文件:
In [212]: with open(path) as f:
.....: lines = [x.rstrip() for x in f]