以下内容主要学习自《利用Python进行数据分析》
第3章 内建数据结构、函数及文件
数据结构和序列
序列是Python中最基本的数据结构,简单但强大。序列中的每个元素都分配一个数字:它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。精通这些数据结构是成为优秀Python编程者的必要条件。
列表
列表是一组可变的Python对象序列,可以简单的理解为可变长度的数组,但比数组更强大的是:列表中的元素可以是不同类型的对象。
创建列表
创建列表最简单的方法就是在方括号中添加元素,并使用逗号隔开:
In[1] : lst = ['a', 'b', 'c', None, '998']
In[2] : lst
out[2] : ['a', 'b', 'c', None, '998']
也可以用list函数
将任意序列或迭代器转换为列表:
In [1]: lst1 = list('hello')
In [2]: lst1
Out[2]: ['h', 'e', 'l', 'l', 'o']
In [3]: lst2 = list(range(10))
In [4]: lst2
Out[4]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
访问列表
通过索引号访问列表,注意:索引号从0开始。
In [1]: lst = ['a', 'b', 'c', None, 985]
# 用索引获取单个元素
In [2]: lst[1]
Out[2]: 'b'
# 获取多个元素,获取的数量等于两个索引的差
In [3]: lst[1:3]
Out[3]: ['b', 'c']
# 不指定开始索引,将从开头获取
In [4]: lst[:2]
Out[4]: ['a', 'b']
# 不指定结束索引,将获取到结尾
In [5]: lst[2:]
Out[5]: ['c', None, 985]
# 索引为负数,将倒数获取
In [6]: lst[-1]
Out[6]: 985
# 获取多个元素,索引为负数
In [7]: lst[-3:-1]
Out[7]: ['c', None]
# 不指定开始索引,结束索引为负数
In [8]: lst[:-1]
Out[8]: ['a', 'b', 'c', None]
修改列表
使用append方法
在列表末尾添加新的对象
in[9]: lst.append(211)
in[10]: lst
out[10]: ['a', 'b', 'c', None, 985, 211]
使用insert方法
在将新的对象添加到列表的指定位置
in[11]: lst.insert(3, 'd')
in[12]: lst
out[12]: ['a', 'b', 'c', 'd', None, 998, 211]
使用reverse方法
反向列表中元素
In [13]: lst.reverse()
In [14]: lst
Out[14]: [211, 998, None, 'd', 'c', 'b', 'a']
使用sort方法
对原列表进行排序,注意:若列表中的对象类型不一样,使用默认参数的排序可能会失败、报错。排序方法的高级应用请参考教材。
In [15]: lst_num = [3, 7, 1, 9, 12]
In [16]: lst_num.sort()
In [17]: lst_num
Out[17]: [1, 3, 7, 9, 12]
删除列表
使用pop方法
移除列表中的某个元素(默认最后一个元素),并且返回该元素的值:
In [18]: item = lst.pop(5)
In [18]: item
Out[18]: 998
In [19]: lst
Out[19]: ['a', 'b', 'c', 'd', None, 211]
使用pop方法
移除列表中的某个元素,注意不是根据索引号移除:
In [20]: lst.remove(None)
In [21]: lst
Out[21]: ['a', 'b', 'c', 'd', 211]
如果对象并不在列表中,使用pop方法
将会报错,因此可以用in
先判断是否包含某对象
In [22]: 'a' in lst
Out[22]: True
In [23]: 'f' in lst
Out[23]: False
列表内置函数
使用len函数
获取列表中元素的总数
使用max函数
获取列表元素最大值
使用min函数
获取列表元素最小值
In [24]: lst = ['a', 'b', 'c', 'd']
In [25]: len(lst)
Out[25]: 4
In [26]: max(lst)
Out[26]: 'd'
In [27]: min(lst)
Out[27]: 'a'
元组
元组是一种固定、不可变的Python对象序列,这意味着元组内的对象是不能改变的,可理解为不可变的数组。
创建元组
创建元组最简单的办法就是在括号中添加元素,并使用逗号隔开:
In[1] : tup = (4, 5 ,6)
In[2] : tup
out[2] : (4, 5, 6)
也可以用tuple函数
将任意序列或迭代器转换为元组:
In[3] : tuple([4, 0, 2])
out[3] : (4, 0, 2)
In[4] : tup = tuple('hello')
In[5] : tup
out[5] : ('h', 'e', 'l', 'l', 'o')
访问元组
访问元组的元素的方法,与“访问列表”的方法一致,可参考上方的“访问列表”。
修改元组
由于元组是不可变的,因此不能对元组中的单个元素进行修改。
可以使用+号连接元组来生成更长的元组:
In[6] : (4, None, 'foo') + (6, 0) + ('bar')
out[6] : (4, None, 'foo', 6, 0, bar')
将元组乘以整数,将生成含有多分拷贝的元组:
In[7] : ('foo', 'bar') * 3
out[7] : ('foo', 'bar', 'foo', 'bar', 'foo', 'bar')
删除元组
元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组
In [8]: del tup
In [9]: tup
NameError: name 'tup' is not defined
元组内置函数
使用len函数
获取元组中元素的总数;
使用max函数
获取元组元素最大值;
使用min函数
获取元组元素最小值。
In [10]: tup = tuple('hello')
In [11]: len(tup)
Out[11]: 5
In [12]: max(tup)
Out[12]: 'o'
In [13]: min(tup)
Out[13]: 'e'
字典
字典也可以称为哈希表或关联数组。字典的大小是可变的,其中包含的每一个元素以键值对形式呈现。字典的值可以是任何Python对象,但字典的键不可变,因此可以用数字、字符串、元组作为键。
创建字典
创建字典最简单的办法就是在大括号中添加元素,并使用逗号隔开,每个元组的键和值用冒号隔开:
In[1] : d1 = {'a' : 'some value', 'b' : [1 ,2, 3, 4]}
In[2] : d1
out[2] : {'a' : 'some value', 'b' : [1 ,2, 3, 4]}
从已有的两个序列生成字典
In[3]: list1 = ['a', 'b', 'c']
In[4]: list2 = [198, 236, 701]
In[5]: d2 = dict(zip(list1, list2))
In[16]: d2
Out[6]: {'a': 198, 'b': 236, 'c': 701}
访问字典
根据键获取值
In[7]: d1['b']
Out[7]: [1, 2, 3, 4]
如果访问一个字典中不存在的键,那么会报告KyeError异常
,为避免这类错误,可以使用in运算符
检查字典中是否含有一个键。
In[8]: 'c' in d1
Out[8]: False
另外,使用get方法
可以在字典中不包含某个键时,返回默认值
In [9]: d1.get('c') # 未指定默认值
Out[9]: None
In [10]: d1.get('c', 0) # 指定默认值
Out[10]: 0
使用keys方法
能获得字典所有的键,使用values
能获得字典所有的值
In[11]: list(d1.keys())
Out[11]: ['a', 'b']
In[12]: list(d1.values())
Out[12]: ['some value', [1, 2, 3, 4]]
修改字典
字典不允许同一个键出现两次(同一个字典中的键都是唯一的),因此,当字典中不存在某键时,会给字典增加新的键值对;如果该键已经存在,那么就会修改它的值。
In[13]: d1['c'] = 17 # 添加新键
In[13]: d1
Out[14]: {'a' : 'some value', 'b' : [1 ,2, 3, 4], 'c' : 17}
In[15]: d1['c'] = 23 # 修改已有键的值
Out[15]: {'a': 'some value', 'b': [1, 2, 3, 4], 'c': 23}
使用update方法
可以合并两个字典。注意:如果原字典中有相同的键,那么它的值将被覆盖,否则,就增加新的键值。
In[16]: d1.update({'b': 'foo', 'd' : 3.1415})
In[17]: d1
Out[17]: {'a': 'some value', 'b': 'foo', 'c': 23, 'd': 3.1415}
删除字典
使用del
删除字典
In [18]: del d1['d']
In [19]: d1
Out[19]: {'a': 'some value', 'b': 'foo', 'c': 23}
使用clear方法
删除字典内所有元素
In [20]: d1.clear()
In [21]: d1
Out[21]: {}
字典内置函数
使用len函数
返回字典中键值对的个数
In[22]: d1 = {'a': 198, 'b': 236, 'c': 701}
In[23]: len(d1)
Out[23]: 3
使用str函数
返回字典的字符串形式
In [24]: str(d3)
Out[24]: "{'a': 198, 'b': 236, 'c': 701}"
集合
集合是一种内容唯一的序列,而列表、元组中的内容是可以重复的。
创建集合
直接创建集合
In [1]: s1 = {'张三', '李四', '李四', '王五', '杨六', '王五'}
In [2]: s1
Out[2]: {'张三', '李四', '杨六', '王五'}
使用set函数
创建集合
In [3]: s2 = set([2, 2, 2, 1, 3, 3])
In [4]: s2
Out[4]: {1, 2, 3}
访问集合
如果想象访问列表、元组那样,使用索引来访问集合,是不行的。会报告TypeError异常
。
In [5]: s1[2]
TypeError: 'set' object does not support indexing
如确实需要获得集合中的元素,可参考如下代码
In [6]: l1 = list(s1) # 把集合转换为列表,然后访问
In [7]: l1[2]
Out[7]: '李四'
In [8]: for item in s1: # 或者采用遍历的方式访问
...: print(item)
...:
王五
张三
李四
杨六
修改集合
添加单个元素
In [9]: s1.add('赵七')
In [10]: s1
Out[10]: {'张三', '李四', '杨六', '王五', '赵七'}
添加多个元素
In [11]: s1.update(['孙九', '孔八'])
In [12]: s1
Out[12]: {'孔八', '孙九', '张三', '李四', '杨六', '王五', '赵七'}
删除集合
使用remove方法
删除集合中的某个元素
In [13]: s1.remove('孔八')
In [14]: s1
Out[14]: {'孙九', '张三', '李四', '杨六', '王五', '赵七'}
此外还有一个方法也是移除集合中的元素,且如果元素不存在,不会发生错误
In [15]: s1.discard('孔八')
使用clear方法
删除集合内所有元素
In [16]: s1.clear()
In [17]: s1
Out[17]: set()
集合运算
使用union方法
得到两个集合的并集
In [18]: s1 = {1, 3, 5, 6, 8}
In [19]: s2 = {2, 4, 5, 6, 7, 9}
In [20]: s1.union(s2)
Out[20]: {1, 2, 3, 4, 5, 6, 7, 8, 9}
使用intersection方法
得到两个集合的交集
In [21]: s1.intersection(s2)
Out[21]: {5, 6}
使用difference方法
得到两个集合的差集
In [22]: s1.difference(s2)
Out[22]: {1, 3, 8}
使用symmetric_difference方法
得到两个集合不重复的元素的集合
In [23]: s1.symmetric_difference(s2)
Out[23]: {1, 2, 3, 4, 7, 8, 9}
使用issubset方法
判断一个集合是否是另一个集合的子集
In [24]: s3 = {3, 8}
In [25]: s3.issubset(s1)
Out[25]: True
使用issuperset方法
判断一个集合是否包含另一个集合
In [26]: s1.issuperset(s3)
Out[26]: True
使用isdisjoint方法
判断两个集合是否没有交集
In [27]: s2.isdisjoint(s3)
Out[27]: True
集合内置函数
使用len函数
返回集合中元素的个数
In[28]: len(s1)
Out[28]: 5
使用str函数
返回字典的字符串形式
In [24]: str(s1)
Out[24]: '{1, 3, 5, 6, 8}'
内建序列函数
拆包
Python提供拆包操作,可以将等号右边的序列包含的元素赋值给变量:
In [1]: lst = [4, 5, 6]
In [2]: a, b, c = lst
In [3]: b
Out[3]: 5
拆包的一个常用场景就是遍历元组或列表中的元素,请参考下方的示例:
In [4]: seq = ((1, 2, 3), (4, 5, 6), (7, 8, 9))
In [5]: 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
注意:拆包操作不仅仅适用于元组,同样适用于列表、集合。上面的示例还演示了元组是可以嵌套的,这同样适用于列表,即可以构建多维序列。
如果你想从序列中“部分拆包”,可以用如下的方法
In [6]: s1 = {1, 3, 5, 6, 8}
In [7]: a, b, *other = s1
In [8]: b
Out [8]: 3
In [9]: other
Out[9]: [5, 6, 8]
# other部分有时是想丢弃的部分,很多Python编程者会使用下划线表示不想要的变量
In [10]: a, b, *_ = s1
enumerate
我们经常需要在遍历一个序列的同时,追踪当前元素的索引,一种老套的方法如下
i = 0
for value in collection:
# 使用value做点事
i+=1
由于这种场景很常见,所以Python内建了enumerate函数
,通过返回(i, value)元组的方法来简化代码
for i, value in enumerate(collection):
# 使用value做点事
zip
列表、集合和字典的推导式
推导式是最受欢迎的Python语言特性之一。它用一种简明的表达式完成从某个列表过滤元素、从而生成一个新的列表。
列表推导式的基本形式为:[expr for val in collection if condition]
集合推导式的基本形式为:{expr for val in collection if condition}
字典推导式的基本形式为:{key-expr : val-expr for val in collection if condition}
列表推导式案例:从给定的字符串列表、选出长度大于3的字符串,并将字符改为大写:
In [1]: strs = ['c', 'python', 'c++', 'java', 'c#']
In [2]: result = [x.upper() for x in strs if len(x)>=3]
In[3]: result
Out[3]: ['PYTHON', 'C++', 'JAVA']
以上的案例,与下面的for循环伪代码是等价的:
result = []
for val in collection:
if condition:
result.append(expr)
嵌套集合推导式案例:从二维列表中,选择姓名包含s、且不重复的名字。
In [4]: all_data = [['Emma', 'Warren', 'Ben', 'Jason', 'Kevin', 'Sophia', 'Joyce', 'Ashley', 'Peter', 'May', 'Abel', 'Ivy', 'Hailey', 'Stella', 'Gloria', 'Denny'], ['Amy', 'Jessie', 'Lucy', 'Johnny', 'Amanda', 'Jennifer', 'Hailey', 'Abby', 'Albert', 'Bruce', 'Paul', 'Charles'], ['Denny', 'Andrew', 'Amanda', 'Abel', 'Kenny', 'Ben', 'Evan', 'Bill', 'Peter', 'Jessie', 'Jason', 'Gloria']]
In [5]: result = {name for names in all_data for name in names if (name.lower()).count('s')>=1}
In [6]: str(result)
Out[6]: "{'Charles', 'Ashley', 'Jessie', 'Stella', 'Jason', 'Sophia'}"
请牢记:推导式for表达式的嵌套顺序,应当和你写嵌套for循环的顺序一致。
函数
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
def 函数名(参数列表):
函数体
return xx
定义Python函数的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()、冒号。
- 任何传入参数和自变量必须放在圆括号中间。
- 函数内容要缩进。
- return 关键字返回一个值给调用方。没有return相当于返回 None。
返回多个值
使用Python编程时,你一定会喜欢上它可以从函数返回多个值。在数据分析和其它科研应用中,会经常需要返回多个值。Python的函数实质上是返回了一个元组对象,而后又拆包为多个结果变量。
In [1]: def f():
a=5
b=6
c=7
return a,b,c
In [2]: x,y,z = f()
In [3]: x
Out[3]: 5
函数是对象
在Python中,函数也是个对象。这种特性使Python可以非常容易的实现在其它语言中较难实现的代码构造。
字符串数据清洗案例:假设有一些姓名数据,要去除每个字符串收尾的空格、去除每个字符串中的特殊符号,并把每个字符串的首字母大写。
传统的编程思路是:编写一个函数、完成既定功能的封装,如下所示:
In [1]: import re # 引入正则表达式模块
In [2]: def clean_strings(strings):
result = []
for value in strings:
value = value.strip() # 去除空格
value = re.sub('[!#?]', '', value) # 使用正则表达式模块,替换特殊字符
value = value.title() # 把首字母转换为大写
result.append(value)
return result
In [3]: names = [' emma ', 'Warren!', '#ben', ' jason', '??Kevin', 'sophia ', '?joyce!']
In [4]: clean_strings(names)
Out[4]: ['Emma', 'Warren', 'Ben', 'Jason', 'Kevin', 'Sophia', 'Joyce']
在Python中,实现以上的需求,还可以这样写:
In [1]: import re # 引入正则表达式模块
In [2]: def remove_punctuation(value):
return re.sub('[!#?]', '', value) # 使用正则表达式模块,替换特殊字符
In [3]: def clean_strings(strings, ops):
result = []
for value in strings:
for function in ops:
value = function(value)
result.append(value)
return result
In [4]: clean_ops = [str.strip, remove_punctuation, str.title] # 把函数名组装在列表中
In [5]: names = [' emma ', 'Warren!', '#ben', ' jason', '??Kevin', 'sophia ', '?joyce!']
In [6]: clearn_strings(names, clean_ops)
Out[6]: ['Emma', 'Warren', 'Ben', 'Jason', 'Kevin', 'Sophia', 'Joyce']
因为“在Python中,函数也是个对象”,所以,在上面的例子中,可以把函数名组装在列表中,然后遍历调用。像这种更为函数化的模式,使clearn_strings函数具有更强的复用性和通用性。
匿名(Lambda)函数
匿名(Lambda)函数是一种通过单个语句生成函数的方式。和用def关键字
声明一个函数不同,匿名函数对象自身没有一个显式的__name__
属性,这就是lambda函数被称为匿名函数的原因。
In [3]: f = lambda x: x * 2
In [4]: ints = [4, 0, 1, 5, 7]
In [5]: [f(x) for x in ints]
Out[5]: [8, 0, 2, 10, 14]
匿名函数的代码量小、意图清晰,在功能不是很复杂的前提下,比写一个完整的函数更加高效。
Lambda应用案例:根据字符串中不同字母的数量进行排序
In [6]: strings = ['foo', 'card', 'bar', 'aaaa', 'abab']
In [7]: strings.sort(key = lambda x: len(set(list(x))))
In [8]: strings
Out[8]: ['aaaa', 'foo', 'abab', 'bar', 'card']
错误和异常处理
优雅地处理错误或异常是构建稳定程序的重要工作。Python对异常处理的伪代码如下:
tyr:
# 要执行的代码
raise ValueError(10) # 抛出一个指定的异常
except ValueError as e:
# 捕获到异常时,对异常进行处置的代码
print(e.value)
finally:
# 无论是否有异常,都要执行的善后代码
另外,except语句
可以捕获指定的一个或多个异常,方法是将异常类型写成元组(封装在小括号中),并跟在except关键字后。
except (TypeError, ValueError):
上面的TypeError
、ValueErorr
,都是Python内置的异常类。更多的内置异常类、创建自定义异常类,请参考Python官方文档。
文件I/O
Python中处理文件非常简单,这也是Python能够在文本和文件处理领域如此流行的原因。
打开并读取文件
Python使用open函数
打开文件,默认情况下,文件是以只读模式"r"打开的。在结束操作时,关闭文件是非常重要的。如下的伪代码:
f = open('readme.txt')
for line in f:
print(line)
f.close()
另一种更简单的关闭文件的方式是使用with语句
,文件会在with代码块结束后自动关闭。
with open('readme.txt') as f:
for line in f:
print(line)
打开并写入文件
Python操作文件的模式有如下几种:
- r:只读模式
- w:只写模式,创建新文件(路径下的同名文件中的数据会被清除)
- x:只写模式,创建新文件(路径下有同名文件会创建失败)
- a:添加到已存在的文件(如果不存在就创建)
- r+:读写模式
- b:二进制文件模式,与别模式搭配使用(如'rb'或'wb')
- t:文本文件模式,与别的模式搭配使用(如'rt'或'wt')
下面的伪代码示例写入文件:
with open('readme.txt', 'w') as f:
f.writelines(strings) # 将字符串序列写入文件