大家好呀,今天是数据分析课程的 Python 基础知识部分第 5 节,主要讲异常、模块和包的相关知识,另外还会涉及到一些文件的基本操作,因为下节课我们会学习用 Python 处理 word 和 excel 文件。
这节课的主要内容有:
1、捕获异常
2、模块
(1)自定义模块的使用
3、包的介绍
4、文件基础操作(不是重点)
(1)文件简介
(2)读文件
(3)文件的打开方式
(4)字符编码
(5)写文件
一、捕获异常
在各种计算机编程语言中,程序一旦出错,系统会一级一级上报,直到某个函数可以处理该错误(比如,给用户输出一个错误信息),才会停止。所以高级语言通常都内置了一套 try...except...finally...
的错误处理机制,Python也不例外。举个例子来看一下 try
的机制:
try:
print('try...')
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')
当我们认为某些代码可能会出错时,就可以用 try
来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即 except
语句块,执行完 except
后,如果有 finally
语句块,则执行 finally
语句块,至此,执行完毕。
上面的代码在计算 10 / 0
时会产生一个除法运算错误:
try...
except: division by zero finally...
END
从输出可以看到,当错误发生时,后续语句 print('result:', r)
不会被执行,except
由于捕获到 ZeroDivisionError
,因此被执行。最后, finally
语句被执行。然后,程序继续按照流程往下走。
如果把除数 0 改成 2 ,则执行结果如下:
try...
result: 5
finally...
END
由于没有错误发生,所以 except
语句块不会被执行,但是 finally
如果有,则一定会被执行(可以没有 finally
语句)。
当然,错误有很多种类,如果发生了不同类型的错误,应该由不同的 except
语句块处理。所以我们可以用多个 except
来捕获不同类型的错误:
try:
print('try...')
r = 10 / int('a')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
finally:
print('finally...')
print('END')
int()
函数可能会抛出 ValueError
,所以我们用一个 except
捕获 ValueError
,用另一个 except
捕获 ZeroDivisionError
。
此外,如果没有错误发生,可以在 except
语句块后面加一个 else
,当没有错误发生时,会自动执行 else
语句:
try:
print('try...')
r = 10 / int('2')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('no error!')
finally:
print('finally...')
print('END')
Python 的错误其实也是 class
,所有的错误类型都继承自 BaseException
,所以在使用 except
时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。比如:
try:
foo()
except ValueError as e:
print('ValueError')
except UnicodeError as e:
print('UnicodeError')
第二个 except
永远也捕获不到 UnicodeError
,因为 UnicodeError
是 ValueError
的子类,如果有,也被第一个 except
给捕获了。
Python所有的错误都是从 BaseException
类派生的,常见的错误类型和继承关系看这里:
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
使用 try...except
捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数 main()
调用 foo()
, foo()
调用 bar()
,结果 bar()
出错了,这时,只要 main()
捕获到了,就可以处理:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')
也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写 try...except...finally
的麻烦。
python所有的标准异常类:
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
SystemExit | Python 解释器请求退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
LookupError | 无效数据查询的基类 |
IndexError | 序列中没有没有此索引(index) |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
NameError | 未声明/初始化对象 (没有属性) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError | Python 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning | 关于特性将会被废弃的警告 |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |
二、模块
模块: 通俗理解,一个 .py
文件就是一个模块,模块是管理功能代码的。
内置模块:就是 python 自己内部自带的不需要我们去下载的模块, 比如:time
, random
等。
1、自定义模块的使用
注意: 自定义模块名字和变量名的定义很类似,都是由字母、数字、下划线组成,但是不能以数字开头,否则无法导入该模块。
创建名为first_module的自定义模块:
__all__ = ["g_num", "show"]
# 指定 all 表示 from 模块名 import * 只能使用指定的功能代码,而不是所有的功能代码
# 定义全局变量
g_num = 10
# 定义函数
def show():
print("我是一个函数")
# 定义类
class Student(object):
def init (self, name, age):
self.name = name
self.age = age
def show_msg(self):
print(self.name, self.age)
# 解决导入的模块中方法没有调用就会执行
if __name__ == '__main__':
show()
使用自定义的模块:
# 导入模块
import first_module
# 使用模块中的功能代码
print(first_module.g_num)
first_module.show()
stu = first_module.Student("李四", 20)
stu.show_msg()
注意:使用 __name__
查看模块名,执行哪个文件,哪个文件中的 __name__
输出 __main__
, 其他导入模块中的 __name__
结果就是模块名字。
模块导入的注意点:
- 自定义的模块名不要和系统的模块名重名;
- 导入的功能代码不要在当前模块定义否则使用不了导入模块的功能代码。
三、包的介绍
包:通俗理解包就是一个文件夹,只不过文件夹里面有一个__init__.py
文件,包是管理模块的,模块是管理功能代码的。
# -----import导入包里面的模块----
import first_package.first
#-----import导入包里面的模块设置别名----
import first_package.first as one
#----from导入包名 import 模块名----
from first_package import second
#--- from 包名.模块名 import 功能代码----
from first_package.first import show # 需要保证当前模块没有导入模块的功能代码
# --- from 包名 import *, 默认不会导入包里面的所有模块,需要在 init 文件里面使用 __all__ 去指定导入的模块
from first_package import *
** __init__
文件写法**
# 如果外界使用from 包名 import * 不会导入包里面的所有模块,需要使用 all 指定
__all__ = ["first", "second"]
# 从当前包导入对应的模块
from . import first
from . import second
四、文件基础操作
1、文件简介
文件包括文本文件和二进制文件(声音,图像,视频)。不过,从存储方式来说,文件在磁盘上的存储方式都是二进制形式,所以,文本文件其实也应该算二进制文件。
不过二者也有区别,虽然都是二进制文件,但是二进制代表的意思不一样。二进制文件是将内存里面的数据直接读写入文本中,而文本文件则是将数据先转换成了字符串,再写入到文本中。
2、读文件
要以读文件的模式打开一个文件对象,使用 Python 内置的 open()
函数,传入文件名和标示符:
>>> f = open('/Users/michael/test.txt', 'r')
标示符 r
表示读,这样,我们就成功地打开了一个文件。
如果文件不存在,open()
函数就会抛出一个 IOError
的错误,并且给出错误码和详细的信息告诉你文件不存在:
>>> f=open('/Users/michael/notfound.txt', 'r')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: '/Users/michael/notfound.txt'
如果文件打开成功,接下来,调用 read()
方法可以一次读取文件的全部内容,Python 把内容读到内存,用一个 str
对象表示:
>>> f.read() 'Hello, world!'
最后一步是调用 close()
方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的:
>>> f.close()
由于文件读写时都有可能产生 IOError
,一旦出错,后面的 f.close()
就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用 try... finally
来实现:
try:
f = open('/path/to/file', 'r') print(f.read())
finally:
if f:
f.close()
但是每次都这么写实在太繁琐,所以,Python 引入了 with
语句来自动帮我们调用 close()
方法:
with open('/path/to/file', 'r') as f:
print(f.read())
这和前面的 try ... finally
是一样的,但是代码更加简洁,并且不必调用 f.close()
方法。
调用 read()
会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用 read(size)
方法,每次最多读取size个字节的内容。另外,调用 readline()
可以每次读取一行内容,调用 readlines()
一次读取所有内容并按行返回 list
。因此,要根据需要决定怎么调用。
如果文件很小, read()
一次性读取最方便;如果不能确定文件大小,反复调用 read(size)
比较保险;如果是配置文件,调用 readlines()
最方便:
for line in f.readlines():
print(line.strip()) # 把末尾的'\n'删掉
3、文件的打开方式
模式 | 描述 |
---|---|
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内 容会被删除。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编 辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容 会被删除。如果该文件不存在,创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编 辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说, 新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结 尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进 行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时 会是追加模式。如果该文件不存在,创建新文件用于读写。 |
ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结 尾。如果该文件不存在,创建新文件用于读写。 |
4、字符编码
要读取非 UTF-8 编码的文本文件,需要给 open()
函数传入 encoding
参数,例如,读取 GBK 编码的文件:
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
>>> f.read()
'测试'
遇到有些编码不规范的文件,你可能会遇到 UnicodeDecodeError
,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况, open()
函数还接收一个 errors
参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
5、写文件
写文件和读文件是一样的,唯一区别是调用 open()
函数时,传入标识符 w
或者 wb
表示写文本文件或写二进制文件:
>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()
你可以反复调用 write()
来写入文件,但是务必要调用 f.close()
来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()
方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用 close()
的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用 with
语句来得保险:
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')
要写入特定编码的文本文件,请给 open()
函数传入 encoding
参数,将字符串自动转换成指定编码。
注意:
以 w
模式写入文件时,如果文件已存在,会直接覆盖(相当于删掉后新写入一个文件)。如果我们希望追加到文件末尾怎么办?可以传入 a
以追加(append)模式写入。