读书笔记:Python 学习手册(5)
结于2021-08-31;
OREILY的书籍,可读性很强,入门类,而且这本书很厚;
- 第七部分 异常和工具
第七部分 异常和工具 ———— 第32章 异常基础
异常时可以改变程序中控制流程的事件;
- 异常会根据错误自动被触发,也能由代码触发和截获;
- 有四个语句处理;
try/except # 捕获异常并恢复
try/except/else
try/finally # 无论 是否异常,执行清理操作
raise # 手动触发异常
assert # 有条件的触发异常
with/as # 作为一种替代方式,实现环境管理器,确保终止行为发生,如打开文件,仅支持实现了环境管理器协议的对象;
异常是一种结构化的“超级goto”
异常的用途:
- 错误处理
- 事件通知
- 特殊情况处理:在异常中处理少数罕见的情况,省去编写应对特殊情况的代码;
- 终止行为:finally
- 非常规控制流程:Pyhton中没有goto,异常有时可以充当类似的角色;
反向追踪:撤销其跳跃前所有的计算结果;Python的异常在进入try语句后,赋值的变量并不会重设;
try:
raise IndexError
# or
assert False, 'IndexError'
except IndexError:
...
print('处理异常后,会恢复执行')
如果没有捕获异常,异常就会向上传递,直到顶层默认的异常处理器,最终终止程序;
用户定义的异常
继承内置异常类Exception:
class BadException(Exception):
pass
异常的终止行为
def after():
try:
raise Exception
finally:
print('after raise')
print('after try')
after()
# 'after raise'
# Traceback....
这个例子中:
- 捕获到异常后,会执行finally语句,由于并未处理异常,异常会被向上传递;因为不会打印after try;
- 如果没有抛出异常,会在打印after raise后,打印after try;
try/except
:用于捕获异常并从中恢复;try/finally
:无论try代码块内是否触发异常,终止行为一定会运行;
可以在同一语句中混合使用except和finally子句;finally一定会执行;
第33章 异常编码细节
新的变化
- try except else finally 混用
- 异常必须编写为类实例;
try:
...
except Exception1:
...
except (Exception2,Exception3):
...
except Exception4 as data:
print("变量data仅在as引入的作用域有效,except语句结束后,会删除")
...
except Exception:
print("会捕获try执行时,所发生的的任何异常,忽略和系统退出相关的异常")
...
except:
print("会捕获try执行时,所发生的的任何异常,甚至包括可能出现的系统异常!")
...
else:
print("没有发生异常时,要执行的处理器")
...
finally:
print("总会运行此代码块")
# 如果发生异常,未捕获,就会在执行finally后,把异常向上传递;
# 如果未发生异常,在执行finally后,在整个try语句后继续执行;
finally
用于定义清理动作,无论异常是否引发或受到处理,都一定会在离开try前运行;
try语法组合:
- 当使用
try except else finally
时,意味包含意下几种情况- try except
- try except else
- try finally
- try except finally
- try except else finally
raise语句
使用raise语句显式地触发异常:
raise <isntance>
:抛出指定类实例raise <class>
:抛出指定类的一个实例,默认调用的构造函数是不带参数的,等价于raise class()
的形式;raise
:重新抛出当前异常,通常用在异常处理器中,以传播已经捕获的异常;
raise IndexError
raise IndexError()
exc = IndexError()
raise exc
# 异常和引发异常的实例会被一起发送
try:
pass
except IndexError as X:
# 使用as 可以让处理器能够访问实例中的数据以及异常类中的方法
print(X)
raise
异常总是通过实例对象来识别;一旦被捕获,就会死掉(不会在传递了),除非由另一个raise或错误重新引发它;
raise语句不包含异常名称或额外数据值时,就是重新引发当前异常;如果不希望异常在程序代码中死掉(传递给更高层的处理器),一般就使用这种形式;
Python3.0 异常链: raise from
raise语句拥有一个可选的from子句:raise exception from otherexception
exception.__cause__
就是otherexception
otherexception.__context__
则是exception
try:
1/0
except Exception as E:
raise TypeError('Bad!') from E
assert 语句
assert通常可视为条件式的raise语句:assert <test>, <data>
,data的部分是可选的;
- 如果
test
为假,Python就会引发异常; data
项,一般作为程序终止时,异常显示的一部分;
# 等效的raise
if __debug__:
if not <test>:
raise AssertionError(<data>)
assert语句时附加功能,如果使用-O Python命令行标志位,会从编译后的字节码中移除;
__debug__
是内置变量名,不使用-O标志,自动为真值;使用类似python -O main.py
的一个命令行来优化模式中运行,可以关闭assert;
assert通常用于验证开发期间的程序状况;几乎都是在收集用户定义的一些约束条件,而不是捕捉内在的程序设计错误(Python会自行收集程序的设计错误);
with/as环境管理器
with及其可选的as子句,也可以用做异常相关的语句,作为常见的try/finally的替代方案;
Python通过环境管理器强化了一些内置工具:如自动关闭文件、自动加解锁,环境管理器可以由用户自定义;
with expresion [as vairable]:
with-block
这里:
- expresion要返回一个支持
环境管理协议
的对象;例如文件对象就有环境管理器,可在with代码块后自动关闭文件,无论是否引发异常;
myfile = open(r"C:\misc/data")
try:
for line in myfile:
pass
finally:
myfile.close()
with open(r"C:\misc/data") as myfile:
for line in myfile:
pass
lock = threading.Lock()
# 环境管理器可以保证锁会在代码块执行前自动获得,并且一旦代码块完成就释放
with lock:
# 存取共享资源
pass
# decimal模块使用环境管理器来简化存储和保存小数配置环境:
# 定义了赋值计算时的精度和取整方式
with decimal.localcontext() as ctx:
ctx.prec = 2
x = decimal.Decimal('1.00') / decimal.Decimal('3.00')
# 语句运行后,当前线程的环境管理器状态自动恢复到语句开始之前的状态
环境管理协议
实现环境管理器,需要使用特殊的方法来接入with语句,该方法属于运算符重载的范畴;
with语句的工作方式:
- 首先,计算表达式得到对象,这个对象被称为环境管理器,它必须有
__enter__
和__exit__
方法; - 调用
__enter__
方法,如果存在as子句,该方法的返回值会被赋值给as子句中的变量; - 执行代码块中的代码;
- 如果代码块引发异常,
__exit__
方法会被调用(带有异常),此方法返回假,则异常会被重新引发,否则,异常终止(无return时,默认返回值是None,即为假); - 如果代码块没有引发异常,
__exit__
方法也会被调用,其type、value、traceback参数都会以None传递;
class TraceBlock:
def message(self, arg):
print('running', arg)
def __enter__(self):
print('starting with block')
return self
def __exit__(self, exc_type, exc_value, exc_tb):
if exc_type is None:
print('exited normally\n')
else:
print('raise an exception!', exc_type)
return False
with TraceBlock() as action:
action.message('text 1')
print('reached')
with TraceBlock() as action:
action.message('text 2')
raise TypeError
print('not reached')
新的contextlib标准模块提供其他工具来编写环境管理器
with嵌套
with A() as a:
with B() as b:
pass
with语句 支持 逗号语法 指定多个环境管理器,来实现嵌套;
实现上边代码的等价形式:
with A() as a, B() as b:
pass
第34章 异常对象
基于类的异常:
- 提供类型分类;
- 超类变成分类的名称,而子类变成这个分类中特定种类的异常;;
- 附加状态信息;
- 支持继承;
自定义的异常继承自内置异常超类:
- 这些超类往往为打印和状态保持提供了有用的默认值;
- 异常必须通过类来定义,要求派生自BaseException内置异常超类,多数情况都是继承这个类的
Exception
子类,以支持针对常规异常的全捕获处理器(会捕获大多数程序应该捕获的内容);
class General(Exception):pass
class Specific(General):pass
def raise0():
raise General()
def raise1():
raise Specific()
for func in (raise0, raise1):
try:
func()
except General:
import sys
print(sys.exc_info()[0])
这里用到了异常处理器sys.exc_info
调用:
- 这是一种抓取最近发生异常的常见方式;
- 其结果的一个元素是引发异常的类;
- 第二个是引发异常的实例,该实例的
__class__
是具体的类; sys.exc_info
通常也与捕获所有内容的空except子句一起使用;
# 在一个模块中定义异常
# mathlib.py
class Divzero(Exception):pass
class Oflow(Exception):pass
def func():
raise Divzero()
# 引用模块 调用方法时 进行捕获处理
# client.py
import mathlib
try:
mathlib.func()
except (mathlib.Divzero, mathlib.Oflow):
pass
上边这段代码,可能是一种常规的使用异常的方式,但是如果定义异常的模块发生变化,如新增了一个异常,就会影响到引用该模块的每个地方;
为了解决这个问题:可以把类异常安排到类树中,使用共同的超类来包含整个类型;
# mathlib.py
class NumErr(Exception):pass
class Divzero(NumErr):pass
class Oflow(NumErr):pass
# client.py
import mathlib
try:
...
except mathlib.NumErr:
...
这样做,如果要新增一个异常也很简单,只需要定义一个NumErr的子类即可;
内置Exception类
Python自身能够引发的所有内置异常,都是预定义的类对象;
BaseException:
- 异常的顶级根类,不能被用户定义的类直接继承;
- 默认构造函数参数作为一个元组存储在args属性中;
Exception:
- 与应用相关的异常的顶级根超类;
- 它是BaseException的一个直接子类,是其他所有内置异常的超类(除系统退出事件类);
- 在try语句的处理器中指明Exception时,会确保你的程序将捕获除了系统退出事件之外的所有异常;
- Exception变成了try语句的一个全捕获,比一条空的ecept更精确;
内置异常分类
内置树类可以让你选择处理器具体或通用的程度;
try:
action()
except Exception:
print('处理所有异常异常')
else:
print('处理非异常的情况')
默认打印和状态
内置异常提供 默认打印显示和状态保持,用户定义的类如果没有重新定义继承的构造函数,传递给构造函数的参数会保存在实例的args元组属性中;打印的时候会自动显示;
raise IndexError('span')
# IndexError: spam
I = IndexError('spam','a')
I.args
# ('spam', 'a')
对于特定的显示和状态保持需求,你总是可以重新定义Exception子类中的__str__
和__init__
这样的继承方法;
class MyBad(Exception):pass
try:
raise MyBad('Mistake')
except MyBad as X:
print(X) # Mistake
# 定制
class MyBad(Exception):
def __str__(self):
return 'Something else'
try:
raise MyBad()
except MyBad as X:
print(X) # Something else
通过except处理器中的as关键字,可以使用变量来访问异常,这提供了一个自然的钩子,以用来为处理器提供数据和行为;
# 这个异常类 可以拥有自己的状态和方法 定制和扩展继承行为
class FormatError(Exception):
logfile = 'formaterror.txt'
def __init__(self, line, file):
self.line = line
self.file = file
def logError(self):
log = open(self.logfile, 'a')
print('Error at', self.file, self.line, file=log)
try:
raise FormatError(42, file='spam.txt')
except FormatError as X:
print(X.file, X.line)
X.logError()
# python xxx.py
# type formaterror.txt
# Error at spam.txt 40
引发异常的异常实例对象,通常作为
sys.exc_info()
调用的结果元组的第二项,这一调用也是返回有关最新引发的异常信息的一个工具;
第35章 异常的设计
try语句是可以嵌套使用的;
Python会在运行时将try语句放入堆栈,发生异常时,Python会回到最近进入、具有相符except分句的try语句;
一旦异常被捕获,只有第一个try有机会对它进行处理;如果有finally子句,那么每个finally代码都会执行;
finally子句不会终止异常,而是指明异常传播过程中,离开每个try语句之前要执行的代码;
错误是异常,但异常并不一定是错误:
- 表示信号的异常,如input函数,在末尾会引发内置的EOFError;
- 调用
sys.exit()
、键盘Ctrl+C,会引发SystemExit和KeyboradInterrupt异常; - 表示警告的异常,如正在使用不推荐的语言功能;
函数调用结果
异常可以提供一种方式来传达结果信号,而不使用返回值:
class FuncCallFailure(Exception):pass
def searcher():
if ...success...:
return ...something...
else:
raise FuncCallFailure()
try:
item = searcher()
except FuncCallFailure:
...report...
else:
...use item...
关闭文件和服务器连接
确保一个特殊代码块的终止操作的更好方式是try/finally
语句:
myfile = open(r'path', 'w')
try:
pass
finally:
myfile.close()
在try外进行开发调试
try:
...
except:
# 出错后 程序仍处于激活状态 便于开发调试
import sys
sys.exc_info()[0]
sys.exc_info()[1]
# log
log = open('testlog','a')
print('something', file=log)
关于sys.exc_info
用于异常处理器获取最近引发的异常;
- 没有处理器,返回元组
(None, None, None)
- 否则,返回元组
(type, value, traceback)
- type:正在处理的异常类型
- value:引发异常的类实例
- traceback是一个对象,代表异常发生时的调用堆栈;
通过as子句所获取的实例的__class__
属性也可以获取异常类型;sys.exc_info
如今主要由空的except使用;
与异常有关的技巧
Python的异常使用很简单,背后的技巧在于,确定except子句要多具体或多通用,以及try语句中要包装多少代码;
- 经常会失败的运算,应该包装到try语句中;
- 偶尔,把大型函数调用放到单个try语句中,而不是让函数本身零散放着若干try语句;
- 使用场景相关,如服务器一般需要持久运行,可能需要try语句捕获异常并从中恢复;
- 用作脚本的话,一般需要完全忽略异常的处理,任何失败都要求关闭脚本;
- 避免使用空except语句,去捕获太多,如捕获不该处理的异常,甚至是捕获到了无关的系统异常,这类异常通常是不该拦截的;
- 应该尽量让 处理器具体化;
- 使用基于类的分类,避免捕获太少的异常;
脚本执行结束正常退出,Python也提供了内置函数
sys.exit(statuscode)
调用来提前终止,实际是引发内置SystemExit
异常来终止程序,使用try finally可以在离开前执行一些关闭操作;但是使用try带空except时,就会阻止这个重要的结束;(注意Exception不是SystemExit的超类)
空except
和 except Exception
都不是良好的捕获异常方式,因为它们能捕获一般的程序设计错误,但这类错误多数时候都应让其通过的;
一个相关的调用
os._exit
也会结束程序,但是是立即终止,跳过清理程序,无法被try except或try finally拦截;通常只用在衍生的子进程中;
核心语言总结
Python工具集:
- 内置工具
- Python扩展,自定义
- 已编译的扩展,基于C/C++这样的外部语言编写的模块;
大型项目开发工具:
- PyDoc和文档字符串
- PyChecker和Pylint,捕捉潜在问题;
- PyUnit(unittext)
- doctest,抽取文档字符串,分解出测试案例和结果,重新执行测试,并验证预期的结果;
- pdb:Python标准库包含的一个命令行代码调试器模块;
发布的选择:
- py2exe
- PyInstaller
- freeze 这三个都可以用来打包字节码和虚拟机,从而形成冻结二进制的独立可执行文件;
其他开发工具,可以浏览官网的PyPI网站;