离线下载
PDF版 ePub版

qyuhen · 更新于 2018-11-28 11:00:43

异常

异常不仅仅是错误,还是一种正常的跳转逻辑。

异常

除多了个可选的 else 分支外,与其他语言并无多大差别。

>>> def test(n):
...     try:
...         if n % 2:
...             raise Exception("Error Message")
...     except Exception as ex:
...         print "Exception:", ex.message
...     else:
...         print "Else..."
...     finally:
...         print "Finally..."

>>> test(1)     # 引发异常,else 分支未执行,finally 总是在最后执行。
Exception: Error Message
Finally...

>>> test(2)     # 未引发异常,else 分支执行。
Else...
Finally...

关键字 raise 抛出异常,else 分支只在没有异常发生时执行。可无论如何,finally 总会被执行。

可以有多个 except 分支捕获不同类型的异常。

>>> def test(n):
...     try:
...         if n == 0:
...             raise NameError()
...         elif n == 1:
...             raise KeyError()
...         elif n == 2:
...             raise IndexError()
...         else:
...             raise Exception()
...     except (IndexError, KeyError) as ex: # 可以同时捕获不同类型的异常。
...         print type(ex)
...     except NameError:   # 捕获具体异常类型,但对异常对象没兴趣。
...         print "NameError"
...     except:     # 捕获任意类型异常。
...         print "Exception"

>>> test(0)
NameError

>>> test(1)
<type 'exceptions.KeyError'>

>>> test(2)
<type 'exceptions.IndexError'>

>>> test(3)
Exception

下面这种写法已经被 Python 3 抛弃,不建议使用。

>>> def test():
...     try:
...         raise KeyError, "message"  # 相当于 KeyError("Message")
...     except (IndexError, KeyError), ex: # 相当于 as ex
...         print type(ex)

支持在 except 中重新抛出异常。

>>> def test():
...     try:
...         raise Exception("error")
...     except:
...         print "catch exception"
...         raise     # 原样抛出异常,不会修改 traceback 信息。

>>> test()
catch exception
Traceback (most recent call last):
    raise Exception("error")
Exception: error

如果需要,可用 sys.exc_info() 获取调用堆栈上的最后异常信息。

>>> def test():
...     try:
...         raise KeyError("key error")
...     except:
...         exc_type, exc_value, traceback = sys.exc_info()
...         sys.excepthook(exc_type, exc_value, traceback) # 显示异常信息

>>> test()
Traceback (most recent call last):
    raise KeyError("key error")
KeyError: 'key error'

自定义异常通常继承自 Exception。应该用具体异常类型表示不同的错误行为,而不是 message这样的状态值。

除了异常,还可以显示警告信息。warnings 模块另有函数用来控制警告的具体行为。

>>> import warnings

>>> def test():
...     warnings.warn("hi") # 默认仅显式警告信息,不会中断执行。
...     print "test..."

>>> test()
UserWarning: hi
test...

断言

断言 (assert) 虽然简单,但远比用 print 输出调试好得多。

>>> def test(n):
...     assert n > 0, "n 必须大于 0" # 错误信息是可选的。
...     print n

>>> test(1)
1

>>> test(0)
Traceback (most recent call last):
    assert n > 0, "n 必须大于 0"
AssertionError: n 必须大于 0

很简单,当条件不符时,抛出 AssertionError 异常。assert 受只读参数 debug 控制,可以在启动时添加 "-O" 参数使其失效。

$ python -O main.py

上下文

上下文管理协议 (Context Management Protocol) 为代码块提供了包含初始化和清理操作的安全上下文环境。即便代码块发生异常,清理操作也会被执行。

  • enter: 初始化环境,返回上下文对象。
  • exit: 执行清理操作。返回 True 时,将阻止异常向外传递。
>>> class MyContext(object):
...     def __init__(self, *args):
...         self._data = args
...
...     def __enter__(self):
...         print "__enter__"
...         return self._data   # 不一定要返回上下文对象自身。
...
...     def __exit__(self, exc_type, exc_value, traceback):
...         if exc_type: print "Exception:", exc_value
...         print "__exit__"
...         return True    # 阻止异常向外传递。

>>> with MyContext(1, 2, 3) as data:  # 将 __enter__ 返回的对象赋值给 data。
...     print data

__enter__
(1, 2, 3)
__exit__

>>> with MyContext(1, 2, 3):   # 发生异常,显示并拦截。
...     raise Exception("data error")

__enter__
Exception: data error
__exit__

可以在一个 with 语句中使用多个上下文对象,依次按照 FILO 顺序调用。

>>> class MyContext(object):
...     def __init__(self, name):
...         self._name = name
...
...     def __enter__(self):
...         print self._name, "__enter__"
...         return self
...
...     def __exit__(self, exc_type, exc_value, traceback):
...         print self._name, "__exit__"
...         return True

>>> with MyContext("a"), MyContext("b"):
...     print "exec code..."

a __enter__
b __enter__
exec code...
b __exit__
a __exit__

contextlib

标准库 contextlib 提供了一个 contextmanager 装饰器,用来简化上下文类型开发。

>>> from contextlib import contextmanager

>>> @contextmanager
... def closing(o):
...     print "__enter__"
...     yield o
...     print "__exit__"
...     o.close()   # 正常情况下要检查很多条件,比如 None,是否有 close 方法等。

>>> with closing(open("README.md", "r")) as f:
...     print f.readline()

__enter__
#学习笔记
__exit__

原理很简单,contextmanager 替我们创建 Context 对象,并利用 yield 切换执行过程。

  • 通过 enter 调用 clsoing 函数,将 yield 结果作为 enter 返回值。
  • yield 让出了 closing 执行权限,转而执行 with 代码块。
  • 执行完毕,exit 发送消息,通知 yield 恢复执行 closing 后续代码。

和第 5 章提到的用 yield 改进回调的做法差不多。contextmanager 让我们少写了很多代码。但也有个麻烦,因为不是自己写 exit,所以得额外处理异常。

>>> @contextmanager
... def closing(o):
...     try:
...         yield o
...     except:
...         pass   # 忽略,或抛出。
...     finally:   # 确保 close 被执行。
...         o.close()

contextlib 已有现成的 closing 可用,不用费心完善上面的例子。

上下文管理协议的用途很广,比如:

  • Synchronized: 为代码块提供 lock/unlock 线程同步。
  • DBContext: 为代码块中的逻辑提供共享的数据库连接,并负责关闭连接。
  • 等等……

如果你从没抛出过自定义异常,那么得好好想想了……

上一篇: 下一篇: 装饰器