
Python 基础五
捕捉异常、模块、包、文件基础错操作
知识点
1、捕捉异常点
一旦出错,还要一级一级上报,知道查到某个函数可以处理该错误(比如,给用户输出一个错误信息)。所以高级语言通常可以内置一套 try..expect..finally. .的错误处理机制,Python也不例外。
1 | try: |
当我们认为某些代码可能会出错时,就可以用 try 来运行这段代码,如果执行出错,则后续代码不会继续执行 ,而是直接跳转至错误处理代码,及 except 语句块,执行完 except 后,如果有 finally 语句块,则执行finally 语句块,至此,执行完毕。
上面的代码在计算 10/0 时会产生一个除法运算错误:
1 | try... |
从输出可以看出,当发生错误时,后续语句 print('result:',r) 不会被执行,except 由于捕捉到 ZeroDivisionError ,因此被执行。最后,fianlly 语句被执行。
如果把 0 改成 2 ,则执行结果如下:
1 | try... |
由于没有错误发生,所以 except 语句块不会被执行,但是 finally 如果有,则一定会被执行(可以没有finally 语句)。
错误应该有多种种类,如果发生了不同类型的错误,应该由不同的 except 语句块处理。
1 | try: |
int() 函数可能会抛出 ValueError,所以可以用一个except 来捕捉 ValueError ,用另一个 except 捕捉 ZeroDivisionError。
此外,如果没有错误发生,可以在 except 后面加一个 else ,当没有错误发生时,会自动执行 else 语句:
1 | try: |
Python 的错误其实也是 class ,所有的错误类型都 继承 自 BaseException ,所以在使用 except 时需要注意的是,它不但捕获该类型的错误,还把其子类都一网打尽,比如:
1 | try: |
第二个 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() 捕获到了,就可以处理:
1 | def foo(s): |
也就是说不需要在每一个可能出错的地方去捕获错误,只要在合适的层次无捕获就可以了,这样可以大大减少写 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 | 用户代码生成的警告 |
2. 模块
模块: 通俗理解一个 .py 文件就是一个模块,模块是管理功能代码的。
内置模块: 就是 Python 自己内部自带的模块,如 time、random 等。
2.1 自定义模块的使用
注意:自定义模块名字和变量名定义很类似,都是由字母、数字、下划线组成,但是不能以数字开头。
创建名为 first_model 的自定义模块
1 | __all__ = ['f_num','show'] |
使用自定义的模块
1 | # 导入模块 |
注意: 使用 __name__ 查看模块名,执行哪个文件,哪个文件中的 __name__输出__main__ ,其他导入的 __name__ 结果就是模块名字。
模块导入注意点:
1. 自定义的模块名不要和系统的模块名重名,
2. 导入的功能代码不要再当前模块定义否则使用不了导入模块的功能代码
3. 包的介绍
包: 通俗理解包就是一个文件夹,只不过文件夹里由一个 init.py 文件,包是管理模块的,模块是管理功能代码的。
1 | # -----import 导入包里的模块----- |
__init__ 文件写法
1 | # 如果外界使用 from 包名 import * ,默认不会导入包里面的所有模块,需要使用 __all__指定 __all__ =['first','second'] |
4. 文件的基本操作
读文件
要以读文件的模式打开一个文件对象,要使用Python 内置的 open() 函数,传入文件名和标识符。
1 | f = open('/Users/python/test.txt','r') |
标识符 ‘r’ 表示读, 这样,就成功地打开了一个文件。
如果文件打开成功,可以用read() 方法读取全部内容。Python 把内容读到内存,用一个str 对象表示
1 | f.read() |
最后一步 调用close() 方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。
1 | f.close() |
由于文件读写时可能产生 IOError ,一旦出错,后面的 f.close() 也就不会调用,所以,为了保证无论是否出错都能正确关闭文件,可以使用 try...finally 来实现
1 | try: |
但是每次这样写太麻烦,所以 Python 引入 with 语句来自动帮我我们调用 close()
1 | with open('/Users/python/test.txt','r') as f: |
调用 read() 会一次性读取文件的全部内容,如果文件很大,内存就爆了。保险起见,可以反复调用 read(size) 方法,每次最多读取size 个字节的内容。另外,readline() 可以每次读取一行内容,调用readlines() 一次读取所有内容并按行返回 list 。
如果文件很小,read() 一次读取最方便;如果不能确定文件大小,反复调用read(size) 比较保险;如果是配置文件,调用 readlines() 最方便。
1 | for line in f.readlines(): |
文件的打开方式
| 模式 | 说明 |
|---|---|
| r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
| w | 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
| rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。 |
| wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
| r+ | 打开一个文件用于读写。文件指针将会放在文件的开头. |
| w+ | 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
| rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。 |
| wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
字符编码
要读取非 UTF-8 编码的文本文件,需要给 open() 函数传入 encoding 参数,例如,读取GBK编码的文件
1 | f= open('/Users/python/test.txt','r',encoding='gbk') |
遇到编码不规范的文件,可能会遇到 UnicodeDecodeError,因为文本文件中可能夹杂了一些非法变法的字符。遇到这种情况,open() 函数还接受一个 errors 参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略。
1 | f = open('/Users/python/test.txt','r',encoding='gbk',errors='ignore') |
写文件
写文件和读文件一样,唯一区别是调用 open() 函数时,传入 表示符 'w' 或者 'wb' 表示写文本文件或者二进制文件:
1 | f = open('/Users/python/test.txt','w') |
可以反复使用 write() 来写入文件,但是必须要使用 f.close() 来关闭文件。当我们写如文件时,操作系统往往不会立马把数据写入磁盘,而是放到缓存里空闲时间再慢慢写入。只有调用 close() 方法时,操作系统才能保证包所有数据写入磁盘。所以还是可以利用 with 语句。
1 | with open('/Users/python/test.txt','w') as f: |
要写入特定编码文本文件,要给 open() 函数传入 encoding 参数,将字符串转换为指定编码
注意: 以'w' 模式写入文件时,如果文件已经存在,会直接覆盖(相当于删掉后新写入一个文件)。可以传入 'a' 来追加 (append)模式写入。