异常#
异常在 Python 中无处不在。事实上在标准 Python 库里面的每个模块都使用它们,而且在很多不同情形下, Python 自身也会抛出异常。贯穿这本书,你会反复的看到它们。
什么是一个异常?通常情况下,它是一个错误,提示某个东西出问题了。(不是所有的异常都是错误,但目前来说别担心那个) 某些程序语言鼓励对错误返回代码的使用,你可以对它进行检查。 Python 鼓励对异常的使用,你可以对它进行处理。
当一个错误发生在 Python Shell 里面的时候,它会打印一些关于这个异常以及它如何发生的详细信息,就此而已。这个被称之为一个 未被处理
的异常。在这个异常被抛出的时候,没有代码注意到并处理它,因此它把它的路径冒出来,返回到 Python Shell
的最顶层,输出一些调试信息,然后圆满结束。在这个 Shell 中,这没什么大不了的,但是如果在你的实际 Python
程序正在运行的时候发生,并且对这个异常没有做任何处理的话,整个程序就会嘎的一声停下来。可能那正是你想要的,也可能不是。☞不像 Java, Python 函数不声明它们可能会抛出哪些异常。它取决于你去判断哪些可能的异常是你需要去捕获的。
一个异常不会造成整个程序崩溃。不过,异常是可以被处理的。
有时候一个异常是真正地由于你代码里面的一个 bug 所引起的(比如访问一个不存在的变量),但有时候一个
异常是你可以预料到的东西。如果你在打开一个文件,它有可能不存在。如果你在导入一个模块,它可能没有被安装。如果你在连接到一个数据库,它有可能是无效
的,或者你可能没有访问它需要的安全认证信息。如果你知道某行代码可能抛出一个异常,你应该使用 try...except 块来处理这个异常。☞Python 使用 try...except 块来处理异常,使用 raise 语句来抛出异常。 Java 和C++使用 try...catch 块来处理异常,使用 throw 语句来抛出异常。
这个 approximate_size() 函数在两个不同的情况下抛出异常:如果给定的size的值大于这个函数打算处理的值,或者如果它小于零。if size
抛出一个异常的语法足够简单。使用 raise 语句,紧跟着异常的名称,和一个人们可以读取的字符串用来调试。这个语法让人想起调用的函数。(实际上,异常是用类来实现的,这个 raise 语句事实上正在创建一个 ValueError 类的实例并传递一个字符串 "number must be non-negative" 到它的初始化方法里面。但是,我们已经有些超前了!)☞你不需要在抛出异常的函数里面去处理它。如果一个函数没有处理它,这个异常会被传递到它的调用函数,然后那个函数的调用函数,等等“在这个堆栈上面。” 如果这个异常从来没有被处理,你的程序将会崩溃, Python 将会打印一个 “traceback” 的标准错误信息,并以此结束。这也可能正是你想要的,它取决于你的程序具体做什么。
捕获导入错误#
其中一个 Python 的内置异常是 ImportError,它会在你试图导入一个模块并且失败的时候抛出。这有可能由于多种原因引起,但是最简单的情况是当在你的 import 搜索路径里面找不到这个模块的时候会发生。你可以用这个来包含可选的特性到你的程序中。例如, 这个 chardet 库 提供字符编码自动检测。也许你的程序想在这个库存在的时候使用它,但是如果用户没有安装,也会优雅地继续执行。你可以使用 try..except 块来做这样的事情。try: import chardetexcept ImportError: chardet = None
然后,你可以用一个简单的 if 语句来检查 chardet 模块是否存在:if chardet: # do somethingelse: # continue anyway
另一个对 ImportError 异常的通常使用是当两个模块实现了一个公共的API,但我们更想要其中一个的时候。(可能它速度更快,或者使用了更少的内存。) 你可以试着导入其中一个模块,并且在这个模块导入失败的时候退回到另一个不同的模块。例如, XML 的章节谈论了两个模块实现一个公共的API,叫做 ElementTree API。 第一个,lxml 是一个第三方的模块,你需要自己下载和安装。第二个, xml.etree.ElementTree 比较慢,但属于 Python 3 标准库的一部分。try: from lxml import etreeexcept ImportError: import xml.etree.ElementTree as etree
在这个 try..except 块的结尾,你导入了某个模块并取名为etree。由于两个模块实现了一个公共的API,你剩下的代码不需要一直去检查哪个模块被导入了。而且由于这个一定会被导入的模块总是叫做etree,你余下的代码就不会被调用不同名称模块的 if 语句所打乱。
⁂
Unbound 变量#
再看看 approximate_size() 函数里面的这行代码:multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
你从不声明这个multiple变量,你只是给它赋值了。这样就可以了,因为 Python 让你那样做。 Python 将不会让你做的是,引用了一个变量,但从不给它赋值。这样的尝试将会抛出一个 NameError 的异常。>>> xTraceback (most recent call last): File "", line 1, in NameError: name "x" is not defined>>> x = 1>>> x1
将来有一天,你会因为这个而感谢 Python 。
⁂
所有的东西都是区分大小写的#
Python 里面所有的名称都是区分大小写的:变量名、函数名、类名、模块名称、异常名称。如果你可以获取它、设置它、调用它、构建它、导入它、或者抛出它,那么它就是区分大小写的。>>> an_integer = 1>>> an_integer1>>> AN_INTEGERTraceback (most recent call last): File "", line 1, in NameError: name "AN_INTEGER" is not defined>>> An_IntegerTraceback (most recent call last): File "", line 1, in NameError: name "An_Integer" is not defined>>> an_inteGerTraceback (most recent call last): File "", line 1, in NameError: name "an_inteGer" is not defined
等等。
⁂
运行脚本#
Python 里面所有东西都是对象。
Python 模块是对象,并且有几个有用的属性。在你编写它们的时候,通过包含一个特殊的仅在你从命令行运行 Python 文件的时候执行的代码块,你可以使用这些属性容易地测试你的模块。看看 humansize.py 的最后几行代码:if __name__ == "__main__": print(approximate_size(1000000000000, False)) print(approximate_size(1000000000000))☞像C语言一样, Python 使用 == 来做比较,用 = 来赋值。不同于C语言的是, Python 不支持内嵌的赋值,所以没有机会出现你本以为在做比较而且意外的写成赋值的情况。
那么是什么使得这个 if 语句特别的呢? 好吧,模块是对象,并且所有模块都有一个内置的属性 __name__。一个模块的 __name__ 属性取决于你怎么来使用这个模块。如果你 import 这个模块,那么 __name__ 就是这个模块的文件名,不包含目录的路径或者文件的扩展名。>>> import humansize>>> humansize.__name__"humansize"
但是你也可以当作一个独立的程序直接运行这个模块,那样的话 __name__ 将是一个特殊的默认值 __main__。 Python 将会评估这个 if 语句,寻找一个值为 true 的表达式,然后执行这个 if 代码块。在这个例子中,打印两个值。c:homediveintopython3> c:python31python.exe humansize.py1.0 TB931.3 GiB
这就是你的第一个 Python 程序!
⁂
深入阅读#PEP 257: Docstring 约定解释了用什么来从大量的 docstring 中分辨出一个好的 docstring。
Python 参考手册解释了为什么说 Python 里面所有东西都是对象,因为有些人是书呆子,喜欢详细地讨论一些东西。