本章讨论Python的内置功能,这些功能本书会⽤到很多。虽然扩展库,⽐如pandas和Numpy,使处理⼤数据集很⽅便,但它们是和Python的内置数据处理⼯具⼀同使⽤的。我们会从Python最基础的数据结构开始:元组、列表、字典和集合。然后会讨论创建你⾃⼰的、可重复使⽤的Python函数。最后,会学习Python的⽂件对象,以及如何与本地硬盘交互。
一、数据结构和序列
1、元组()
(元组本身是不可变的,但当元组存储的对象是可变对象,那就可以对其中的可变对象进行修改)
2、拆分元组
3、tuple方法
4、列表 [ ]
5、添加和删除元素
6、串联和组合列表
7、排序
8、二分搜索和维护已排序的列表
bisect(c,2),是指在保证排序的情况下,将2插入c的位置(从0开始)
返回4就是说,可以2可以插在第四个位置,不会影响排序。
9、切片
10、序列函数
1、enumerate函数
2、sorted函数
sorted函数是生成一个副本,而sort是对原对象进行排序。
3、zip函数(主要用在for循环中的in,将多个列表、元组组合成一个方便使用)
3、reversed函数
11、字典 { }
字典由键-值组成,值可以是任意一个对象(元组、列表、数字、字符串),键也可以是(数字、字符)
你可以像访问列表或元组中的元素⼀样,访问、插⼊或设定字典中的元素:
In [104]: d1[7] = 'an integer'
In [105]: d1
Out[105]: {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'}
In [106]: d1['b']
Out[106]: [1, 2, 3, 4]
你可以⽤检查列表和元组是否包含某个值得⽅法,检查字典中是否包含某个键:
In [107]: 'b' in d1
Out[107]: True
可以⽤ del 关键字或 pop ⽅法(返回值得同时删除键)删除值:
In [108]: d1[5] = 'some value'
In [109]: d1
Out[109]:
{'a': 'some value',
'b': [1, 2, 3, 4],
7: 'an integer',
5: 'some value'}
In [110]: d1['dummy'] = 'another value'
In [111]: d1
Out[111]:
{'a': 'some value',
'b': [1, 2, 3, 4],
7: 'an integer',
5: 'some value',
'dummy': 'another value'}
In [112]: del d1[5]
In [113]: d1
Out[113]:
{'a': 'some value',
'b': [1, 2, 3, 4],
7: 'an integer',
'dummy': 'another value'}
In [114]: ret = d1.pop('dummy')
In [115]: ret
Out[115]: 'another value'
In [116]: d1
Out[116]: {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'}
keys 和 values 是字典的键(keys)和值(values)的迭代器⽅法。虽然键-值对没有顺序,这两个⽅法可以⽤相同的顺序输出键和值:
In [117]: list(d1.keys())
Out[117]: ['a', 'b', 7]
In [118]: list(d1.values())
Out[118]: ['some value', [1, 2, 3, 4], 'an 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 的键的旧的值都会被舍弃。
12、用序列创建字典
因为字典本质上是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}
后⾯会谈到 dict comprehensions ,另⼀种构建字典的优雅⽅式。
13、默认值
get默认会返回None,如果不存在键,pop会抛出⼀个例外。关于设定值,常⻅的情况是在字典的值是属于其它集合,如列表。
例如,你可以通过⾸字⺟,将⼀个列表中的单词分类:
In [123]: words = ['apple', 'bat', 'bar', 'atom', 'book']
In [124]: by_letter = {}
In [125]: for word in words:
.....: letter = word[0]
.....: if letter not in by_letter:
.....: by_letter[letter] = [word]
.....: else:
.....: by_letter[letter].append(word)
.....:
In [126]: by_letter
Out[126]: {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}
setdefault ⽅法就正是⼲这个的。前⾯的for循环可以改写为:
for word in words:
letter = word[0]
by_letter.setdefault(letter, []).append(word)
14、有效的键类型(可哈希性)
字典的值可以是任意Python对象,⽽键通常是不可变的标量类型(整数、浮点型、字符串)或元组(元组中的对象必须是不可变的)。 这被称为 “可哈希性”。
可以⽤ hash 函数检测⼀个对象是否是可哈希的(可被⽤作字典的键):
I
n [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)
<ipython-input-129-800cd14ba8be> in <module>()
----> 1 hash((1, 2, [2, 3])) # fails because lists are mutable
TypeError: unhashable type: 'list'
要⽤列表当做键,⼀种⽅法是将列表转化为元组,只要内部元素可以被哈希,它也就可以被哈希:
In [130]: d = {}
In [131]: d[tuple([1, 2, 3])] = 5
In [132]: d
Out[132]: {(1, 2, 3): 5}
15、集合 { }
集合是⽆序的不可重复的元素的集合。你可以把它当做字典,但是只有键没有值。 可以⽤两种⽅式创建集合:通过set函数或使⽤尖括号set语句:
集合具有数学的性质(唯一性)
16、列表、集合和字典推导式
filter条件可以被忽略,只留下表达式就⾏。例如,给定⼀个字符串列表,我们可以过滤出⻓度在2及以下的字符串(就是选择长度>=3的字符串),并将其转换成⼤写:
In [154]: strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
In [155]: [x.upper() for x in strings if len(x) > 2]
Out[155]: ['BAT', 'CAR', 'DOVE', 'PYTHON']
⽤相似的⽅法,还可以推导集合和字典。字典的推导式如下所示:
dict_comp = {key-expr : value-expr for value in collection if condition}
集合的推导式与列表很像,只不过⽤的是尖括号:
set_comp = {expr for value in collection if condition}
与列表推导式类似,集合与字典的推导也很⽅便,⽽且使代码的读写都很容易。来看前⾯的字符串列表。假如我们只想要字符串的⻓度,⽤集合推导式的⽅法⾮常⽅便:
In [156]: unique_lengths = {len(x) for x in strings}
In [157]: unique_lengths
Out[157]: {1, 2, 3, 4, 6}
map 函数可以进⼀步简化:
In [158]: set(map(len, strings))
Out[158]: {1, 2, 3, 4, 6}
作为⼀个字典推导式的例⼦,我们可以创建⼀个字符串的查找映射表以确定它在列表中的位置:
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}
17、嵌套列表推导式
假设我们有⼀个包含列表的列表,包含了⼀些英⽂名和⻄班⽛名:
In [161]: 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 [162]: result = [name for names in all_data for name in names
.....: if name.count('e') >= 2]
In [163]: result
Out[163]: ['Steven']
嵌套列表推导式看起来有些复杂。列表推导式的for部分是根据嵌套的顺序,过滤条件还是放在最后。下⾯是另⼀个例⼦,我们将⼀个整数元组的列表扁平化成了⼀个整数列表:
In [164]: some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
In [165]: flattened = [x for tup in some_tuples for x in tup]
In [166]: flattened
Out[166]: [1, 2, 3, 4, 5, 6, 7, 8, 9]
记住,for表达式的顺序是与嵌套for循环的顺序⼀样(⽽不是列表推导式的顺序):
flattened = []
for tup in some_tuples:
for x in tup:
flattened.append(x)
你可以有任意多级别的嵌套,但是如果你有两三个以上的嵌套,你就应该考虑下代码可读性的问题了。分辨列表推导式的列表推导式中的语法也是很重要的:
In [167]: [[x for x in tup] for tup in some_tuples]
Out[167]: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
这段代码产⽣了⼀个列表的列表,⽽不是扁平化的只包含元素的列表。
二、函数
函数是Python中最主要也是最重要的代码组织和复⽤⼿段。作为最重要的原则,如果你要重复使⽤相同或⾮常类似的代码,就需要写⼀个函数。通过给函数起⼀个名字,还可以提⾼代码的可读性。
函数使⽤ def 关键字声明,⽤ return 关键字返回值:
def my_function(x, y, z=1.5):
if z > 1:
return z * (x + y)
else:
return z / (x + y)
同时拥有多条return语句也是可以的。如果到达函数末尾时没有遇到任何⼀条return语句,则返回None。
1、位置参数与关键字参数
函数可以有⼀些位置参数(positional)和⼀些关键字参数(keyword)。关键字参数通常⽤于指定默认值或可选参数。在上⾯的函数中,x和y是位置参数,⽽z则是关键字参数。也就是说,该函数可以下⾯这两种⽅式进⾏调⽤:
my_function(5, 6, z=0.7)
my_function(3.14, 7, 3.5)
my_function(10, 20)
函数参数的主要限制在于:关键字参数必须位于位置参数(如果有的话)之后。你可以任何顺序指定关键字参数。也就是说,你不⽤死记硬背函数参数的顺序,只要记得它们的名字就可以了。
也可以⽤关键字传递位置参数。 前⾯的例⼦,也可以写为:
my_function(x=5, y=6, z=7)
my_function(y=6, x=5, z=7)
这种写法可以提⾼可读性。
2、命名空间、作用域和局部函数
函数可以访问两种不同作⽤域中的变量:全局(global)和局部(local)。Python有⼀种更科学的⽤于描述变量作⽤域的名称,即命名空间(namespace)。任何在函数中赋值的变量默认都是被分配到局部命名空间(local namespace)中的。 局部命名空间是在函数被调⽤时创建的,函数参数会⽴即填⼊该命名空间。
在函数执⾏完毕之后,局部命名空间就会被销毁(会有⼀些例外的情况,具体请参⻅后⾯介绍闭包的那⼀节)。看看下⾯这个函数:
def func():
a = []
for i in range(5):
a.append(i)
调⽤func()之后,⾸先会创建出空列表a,然后添加5个元素,最后a会在该函数退出的时候被销毁。假如我们像下⾯这样定义a:
a = []
def func():
for i in range(5):
a.append(i)
虽然可以在函数中对全局变量进⾏赋值操作,但是那些变量必须⽤global关键字声明成全局的才⾏:
In [168]: a = None
In [169]: def bind_a_variable():
.....: global a
.....: a = []
.....: bind_a_variable()
.....:
In [170]: print(a)
注意:我常常建议⼈们不要频繁使⽤global关键字。因为全局变量⼀般是⽤于存放系统的某些状态的。如果你发现⾃⼰⽤了很多,那可能就说明得要来点⼉⾯向对象编程了(即使⽤类)。
3、返回多个值
在我第⼀次⽤Python编程时(之前已经习惯了Java和C++),最喜欢的⼀个功能是:函数可以返回多个值。下⾯是⼀个简单的例⼦:
def f():
a = 5
b = 6
c = 7
return a, b, c
a, b, c = f()
在数据分析和其他科学计算应⽤中,你会发现⾃⼰常常这么⼲。该函数其实只返回了⼀个对象,也就是⼀个元组,最后该元组会被拆包到各个结果变量中。在上⾯的例⼦中,我们还可以这样写:
return_value = f()
这⾥的return_value将会是⼀个含有3个返回值的三元元组。此外,还有⼀种⾮常具有吸引⼒的多值返回⽅式——返回字典:
def f():
a = 5
b = 6
c = 7
return {'a' : a, 'b' : b, 'c' : c}
取决于⼯作内容,第⼆种⽅法可能很有⽤。