2021-08-31 读书笔记:Python 学习手册(5)

读书笔记: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的超类)

exceptexcept Exception都不是良好的捕获异常方式,因为它们能捕获一般的程序设计错误,但这类错误多数时候都应让其通过的;

一个相关的调用os._exit也会结束程序,但是是立即终止,跳过清理程序,无法被try except或try finally拦截;通常只用在衍生的子进程中;

核心语言总结

Python工具集:

  • 内置工具
  • Python扩展,自定义
  • 已编译的扩展,基于C/C++这样的外部语言编写的模块;

大型项目开发工具:

  • PyDoc和文档字符串
  • PyChecker和Pylint,捕捉潜在问题;
  • PyUnit(unittext)
  • doctest,抽取文档字符串,分解出测试案例和结果,重新执行测试,并验证预期的结果;
  • pdb:Python标准库包含的一个命令行代码调试器模块;

发布的选择:

  • py2exe
  • PyInstaller
  • freeze 这三个都可以用来打包字节码和虚拟机,从而形成冻结二进制的独立可执行文件;

其他开发工具,可以浏览官网的PyPI网站;


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值