目录
一、错误和异常
程序执行时有可能出错,一旦出错,严重可导致程序崩溃,造成不可预期的破坏。不过,并不是所有的错误都是致命的,我们并不希望自己的程序因一些非致命错误而终止,而是希望能用一种相对“和谐”的方式来处理这些错误。在 Python 中,可以将出错归为两类:错误(Errors)和异常(Exceptions)。
错误
从软件方面来说,一般将错误分为两种:语法错误、逻辑错误。
语法错误,指的是程序不符合编程语言的语法规范,导致不能被解释器解释或者编译器无法编译。这些错误违背了语法规则,必须在程序执行前纠正,属于比较低级的错误,在编写程序时,可借助 IDE 进行语法检测,避免错误流到下游。举一个例子:
#少了一个冒号:
while True
print("hello world!")
运行结果:
while True
^
SyntaxError: invalid syntax
逻辑错误,在编程时没有充分考虑应用场景,导致程序逻辑不完整,或者输入不合法等。这类错误通常会导致程序无法达成期望的结果,在编程前应该通过周密的设计进行避免。
#比较两个数,返回较大值,漏掉了相等的情况
def compare(num1, num2):
if (num1 > num2):
return num1
elif (num1 < num2):
return num2
print("result:",compare(12, 12))
运行结果:
result: None
异常
异常是在程序运行中产生的,大多数的异常都不会被程序处理,并以错误信息的形式抛出,Python 中常见的异常有:
异常名称 | 说明 |
---|---|
ZeroDivisionError | 除数为0 |
AttributeError | 试图访问一个对象没有的属性,比如foo.x,但是foo没有属性x |
IOError | 输入/输出异常;常见原因是无法打开文件 |
ImportError | 无法引入模块或包;常见原因是路径问题或名称错误 |
IndentationError | 语法错误(的子类);常见原因是代码没有正确对齐 |
IndexError | 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] |
KeyError | 试图访问字典里不存在的键 |
KeyboardInterrupt | Ctrl+C被按下 |
NameError | 使用一个还未被赋予对象的变量 |
TypeError | 传入对象类型与要求的不符合 |
UnboundLocalError | 试图访问一个还未被设置的局部变量,常见原因是由于另有一个同名的全局变量,导致你以为正在访问它 |
ValueError | 传入一个调用者不期望的值,即使值的类型是正确的 |
举个例子:
num_list = [12,34,56,78,90]
print(num_list[14])
执行结果:
print(num_list[14])
IndexError: list index out of range
异常的处理
对于非致命异常,可以不终止程序,取代以“和谐”的处理方式来解决:捕获程序运行中出现的异常,并针对这种异常进行特定的处理,使程序得以继续执行而不至于终止。实例如下:
def compare(num1, num2):
try:
if(num1 >= num2):
return num1
else:
return num2
except:
return "ERROR"
#正确的调用
print("compare(12,34):",compare(12,34))
#异常的调用
print("compare(12,'ab'):",compare(12,'ab'))
print("compare('12',34):",compare('12',34))
执行结果:
compare(12,34): 34
compare(12,'ab'): ERROR
compare('12',34): ERROR
很明显,程序没有因异常调用而终止,因为我们使用 try-except 语句捕获了 TypeError,从而避免程序异常终止。try-except 这种异常处理机制可以让程序在不牺牲可读性的前提下增强健壮性和容错性,try-except 语句的一般形式如下。
#指定捕获某种异常,并对其进行处理(except子句负责处理)
try:
<statement-1>
.
<statement-N>
except ErrorType:
<statement-x>
.
<statement-y>
#同时捕获多种指定异常,多个异常需要用括号括起来
try:
<statement-1>
.
<statement-N>
except (ErrorType1,ErrorType2,…,ErrorTypeN):
<statement-x>
.
<statement-y>
#不指定异常,默认捕获所有异常,如果对于一段代码,你不清楚会出现什么异常,或者出现多种异常而不需要分别处理,可以采用这种方式,捕获所有异常。
try:
<statement-1>
.
<statement-N>
except:
<statement-x>
.
<statement-y>
try-except处理异常的流程为:
- 首先,try 和 except 之间的语句会被执行;
- 如果没有异常发生,except 后面的语句会被忽略;
- 如果有异常发生,try 后面的其它语句就会被跳过,如果异常的类型与 except 关键词后面的异常匹配,这个 except 后面的语句就会被执行;
- 如果没有异常发生,else 子句存在的话,else 子句将被执行;
- finally 子语会在 try 子句执行完毕之后执行,不管 try 子句是否出现异常。如果一个异常发生在 try 子句中却未被处理(捕获),或者发生在 except 或者 else 子句中时,finally 子句执行完后会再次抛出该异常。
补充说明:
- try 与 else,finally 结合使用的内容将在后面进一步详细说明;
- 一次性捕获多个异常时,多个异常需要用括号括起来;
- 最后一个 except 子句可以不带异常类型,这样就会捕获所有异常;
- 当一个异常发生时,可能它还有一些异常参数,except 语句异常名称后面可以跟一个参数,这个参数会与异常实例绑定,存储在 instance.args 中,如果异常中
__str__()
定义过了,就可以直接打印出参数。
Try 与 else 语句结合使用
Try 语句可以结合 else 一起使用,如果 try 子句没有异常发生,else 子句存在的话,else 子句将被执行。
如下实例:
def compare(num1, num2):
try:
if(num1 >= num2):
result = num1
else:
result = num2
except:
return "ERROR"
else:
print("OK")
return result
#正确的调用
print("compare(12,34):",compare(12,34))
#异常的调用
print("compare(12,'ab'):",compare(12,'ab'))
print("compare('12',34):",compare('12',34))
执行结果:
OK
compare(12,34): 34
compare(12,'ab'): ERROR
compare('12',34): ERROR
try 与 finally 语句结合使用
不管 try 子句是否出现异常,finally 子语都会执行。适用场景:不管 try 是否出现异常,都需要做某种处理,比如,打开一个文件并读取内容,不管这个过程是否出现异常,都需要关闭文件,关闭文件的动作就可以放在 finally 中。
实例如下:
import sys
#在该Python源文件同目录下新建一个文件myfile.txt,写入内容“user-name: Jack”作为测试
try:
#打开文件,读取第一行
f = open('myfile.txt')
s = f.readline()
print("result",s)
except:
print("ERROR")
finally:
print("close file")
f.close()
执行结果:
result user-name: Jack
close file
如果一个异常发生在 try 子句中却未被处理(捕获),或者发生在 except 或者 else 子句中时,finally子句执行完后会再次抛出该异常。
如下实例:
import sys
try:
#打开一个不存在的文件
f = open('file.txt')
s = f.readline()
print("result",s)
except TypeError:
print("ERROR")
finally:
print("close file")
执行结果:没有捕获的异常会被 finally 再次抛出。
close file
Traceback (most recent call last):
File "D:\User\testI\__init__.py", line 28, in <module>
f = open('file.txt')
FileNotFoundError: [Errno 2] No such file or directory: 'file.txt'
Exception 万能异常
上面已经介绍了类似“万能异常”的方式:try-except,不指明捕获异常的类型时,默认捕获所有异常。那么,为什么还需要 Exception 万能异常呢?很多场景下,当异常发生时,我们希望能够记录下更多的异常信息,以便定位问题。
如下实例:
import sys
try:
#打开一个不存在的文件,将抛出异常
f = open('file.txt')
s = f.readline()
print("result",s)
except FileNotFoundError as err:
print("Exception info:",err)
finally:
print("close file")
执行结果:
Exception info: [Errno 2] No such file or directory: 'file.txt'
close file
不仅捕获了异常,而且记录下了异常的详细信息,对于定位问题十分有用。上面例子指定捕获 FileNotFoundError 异常,前提是,开发者十分清楚代码可能出现的异常类别,如果不清楚异常的类别,我们可以使用万能异常。
如下实例:
import sys
try:
#打开一个不存在的文件,将抛出异常
f = open('file.txt')
s = f.readline()
print("result",s)
except Exception as err:
print("Exception info:",err)
finally:
print("close file")
执行结果:
Exception info: [Errno 2] No such file or directory: 'file.txt'
close file
主动抛出异常
前面介绍的都是处理异常的方法,在实际应用中,开发者有些时候还需要主动抛出异常,如外部输入不合法,文件路径不正确等场景,开发者主动抛出异常,调用者根据抛出的异常做针对性处理。
如下实例:
import sys
try:
#输入一个文件名
fileName = input("please input file name:")
splitList = fileName.split('.')
fileType = splitList[splitList.__len__()-1]
#判断文件格式,如果不是doc则抛出异常
if (fileType != "doc"):
raise NameError("the file type:%s is not expected."%(fileType))
f = open(fileName)
s = f.readline()
print("result",s)
except FileNotFoundError as err:
print("Exception info:",err)
finally:
print("close file")
执行结果:NameError 没有被捕获,因此抛出。
please input file name:file.txt
close file
Traceback (most recent call last):
File "D:\Users\ testI\__init__.py", line 33, in <module>
raise NameError("the file type:%s is not expected."%(fileType))
NameError: the file type:txt is not expected.
自定义异常
所谓自定义异常,就是通过直接继承或者间接继承 Exception 类,来创建自己的异常。
如下实例:
#自定义异常,继承自Exception
class MyException(Exception):
def __init__(self, *args):
self.args = args
try:
raise MyException("the file type is not expected.")
except MyException1 as err:
print(err)
执行结果:
the file type is not expected.
在上面的例子中,类 Exception 默认的 __init__()
被覆盖。
当创建一个模块有可能抛出多种不同的异常时,一种通常的做法是为这个包建立一个基础异常类,然后基于这个基础类为不同的错误情况创建不同的子类。
如下实例:
#自定义一个基础异常类
class MyException(Exception):
def __init__(self, *args):
self.args = args
#定义不同种类的业务异常,继承基础异常类
class loginError(MyException):
def __init__(self, code = 100, message = 'login error', args = ('Internal Server Error','http:500')):
self.args = args
self.message = message
self.code = code
class loginoutError(MyException):
def __init__(self):
self.args = ('Internal Server Error',)
self.message = 'login out error'
self.code = 200
#raise loginError(),使用默认参数
try:
raise loginError()
except loginError as e:
print(e) #输出异常
print(e.code) #输出错误代码
print(e.message)#输出错误信息
#raise loginError(),传入参数
try:
raise loginError(400,'password is wrong!',('Internal Server Error',))
except loginError as e:
print(e) #输出异常
print(e.code) #输出错误代码
print(e.message)#输出错误信息
执行结果:
('Internal Server Error', 'http:500')
100
login error
Internal Server Error
400
二、模块和标准库
Python 的核心非常强大,提供了很多内建的工具,Python 标准安装中包括一组模块,如之前介绍过的 math、sys 等,称为标准库(Standard Library),同时,标准库也包含其它的模块。
Import语句与模块
通过 import 语句从外部模块中获取函数并为自己的程序所用。
#通过import导入math模块,如此就可以使用math模块中的函数
import math
#输入一个正数,并计算输出其平方根
x = int(input("Please enter a positive number:"))
#调用了math模块中的函数sqrt(x)
print(math.sqrt(x))
执行结果:
Please enter a positive number:144
12.0
import 语句还可以一次导入多个模块,形式如下,注意模块名之间逗号隔开。
import module1, module2,... moduleN
编写一个极简的模块
任何一个 Python 程序都可以作为模块导入。举一个极简的例子:在同一个包中编写两个 Python 程序模块 hello.py 和 test.py(注意,要在同一个包中,否则需要添加路径)。
Hello.py 代码如下:
#hello.py模块代码,打印hello world!
print('hello, world!')
Test.py 代码如下:
#test.py模块代码,导入hello模块
import hello
运行 test.py 模块,执行结果如下:
hello, world!
执行结果所示,在 test.py 中通过 import hello,执行了 hello.py 中的程序。但是,如果在 test.py 中添加两条 import hello,也只会打印一次“hello world!”。这是因为导入模块并不意味着在导入时执行某些操作(如,打印信息),导入的意义在于定义,如定义变量、函数等,既然是定义,只需要一次即可,导入模块多次和一次效果并没有什么不同。
再来看一个例子,我们在 hello.py 中编写以下代码:
#hello.py模块代码
def printInfo():
print('hello, world!')
def printSum(x,y):
print(x+y)
在 test.py 中编写以下代码:
#test.py模块中的代码
#导入hello模块
import hello
#调用hello模块中的函数
hello.printInfo()
hello.printSum(12, 13)
运行 test.py 模块,执行结果:
hello, world!
25
执行结果所见,通过 import 导入 hello 模块,进而调用了 hello 模块中的函数,似曾相似吧?与导入 math 并调用 math.sqrt(x) 如出一辙。通过上述两个例子,不难理解模块的概念和用法。
模块的位置
上面的例子中,特意强调 hello.py 和 test.py 必须在同一个包中,这是因为在 test.py 中导入 hello 模块,但是并没有指明 hello 模块的位置,程序运行时,Python 解释器如何查找 hello 模块呢?有两种方式可以确保 Python 解释器查找到 hello 模块:第一,将 hello 模块放置于合适的位置(如,同一个包中);第二,显式的告诉解释器 hello 模块的位置。
1.将模块放置于正确的位置。
所谓正确的位置,就是 Python 解释器查找模块的位置,只要知道 Python 解释器查找模块的路径,然后将自己的模块放置到对应的路径下即可。Python 解释器的搜索路径可以通过 sys 模块的 path 变量获取,如下代码:
#通过下面的代码,打印搜索路径
import sys
for temp in sys.path:
print(temp)
执行结果:
D:\Users\userName\workspace1\PythonLearning\testI
D:\Users\userName\workspace1\PythonLearning
D:\Program_file\Python\Python36\DLLs
D:\Program_file\Python\Python36\lib
D:\Program_file\Python\Python36
D:\Program_file\Python\Python36\lib\site-packages
D:\Program_file\Python\Python36\lib\site-packages\win32
D:\Program_file\Python\Python36\lib\site-packages\win32\lib
D:\Program_file\Python\Python36\lib\site-packages\Pythonwin
D:\Program_file\Python\Python36\python36.zip
上面的路径中,PythonLearning 是示例代码所在的 project 名,testI 是示例代码所在的 package 名,hello.py 和 test.py 就是在这个包中。除此以外,搜索路径列表里面还有标准库路径、插件路径,只要将用户自己编写的模块放置到上述路径下,Python 解释器就可以搜索到。
正因为搜索路径列表的存在,在使用标准库模块和同一个包中的模块时,直接通过 import 导入即可,不必显式声明模块所在的路径。
2.告诉解释器去哪里找。
“将模块放置到正确的位置”虽然简单,但在一些特殊的场景下并不适用:
- 没有在 Python 解释器目录中存放文件的权限;
- 希望将模块放到自定义的位置。
关于“告诉解释器去哪里找”,有两种方式:编辑 sys.path 或者编辑 PYTHONPATH 环境变量。
- 编辑 sys.path,如下实例:
import sys
#添加路径/home/python/test到path中
sys.path.append('/home/python/test')
- 编辑 PYTHONPATH 环境变量。
环境变量并不是 Python 解释器的组成,而是操作系统的元素,因此,编辑环境变量的操作会因操作系统的不同而有所差异,以 Linux 操作系统为例,添加环境变量的命令如下,可将命令写入 shell 脚本中,在需要时,执行脚本即可。
#添加路径/home/python/test到环境变量中,注意冒号隔开
export PYTHONPATH=$PYTHONPATH:/home/python/test
from…import 语句
如果一个模块很大(如,包含几百个函数),而我们只需要使用其中一小部分,那么将整个模块导入就很不划算,这种场景下,我们可以使用 from…import 语句来指定要导入的部分,一般形式如下:
from modname import name1, name2, ... nameN
再来看一个实例。
在 hello.py 中编写以下代码:
#hello.py模块代码
def printInfo():
print('hello, world!')
def printSum(x,y):
print(x+y)
在 test.py 中编写以下代码:
#test.py模块中的代码
#从hello模块中导入printInfo函数
from hello import printInfo
#调用hello模块中的printInfo函数
printInfo()
运行 test.py 模块,执行结果:
hello, world!
备注: from…import 语句还有一种特殊的用法:from…import*
,即导入一个模块中的所有不以下划线开头的全局名称到当前的命名空间,后文将予以说明。
深入模块
Python 中除了标准库提供的模块以外,还有大量的第三方模块,并且模块的数量还在不断增长,现有模块也在不断迭代改进,因此,掌握分析模块的方法十分必要,将有助于理解和使用模块。
dir() 函数
可以通过 dir() 函数查看模块中包含的内容,它会将模块中所有的函数,类变量等列出。如下例子:
import math
print(dir(math))
执行结果(截取了一部分):
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos',…,'trunc']
其中,一些名字以下划线开始,表示它们并不是为模块外部使用而准备的(可狭义地理解为非对外接口)。
__name__
属性
每个模块都有一个 __name__
属性,当其值是 __main__
时,表明该模块自身在运行,否则是被引入。一个模块被另一个程序第一次引入时,其主程序将运行。如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用 __name__
属性来使该程序块仅在该模块自身运行时执行。如下实例:
在 hello.py 中编写以下代码:
#hello.py模块代码
def printInfo():
print('hello, world!')
if __name__ == '__main__':
print('native')
else:
print('external')
在test.py中编写以下代码:
#test.py模块中的代码
#导入hello模块
import hello
#调用hello模块中的printInfo函数
hello.printInfo()
运行 test.py 模块,执行结果:
external
hello, world!
运行 hello.py 模块,执行结果:
native
__all__
变量
一个模块中有很多变量和函数,但有些变量和函数仅被期望于模块内部使用,而不是作为对外公有接口。一个模块对外的公有接口可以通过 __all__
变量来定义,在导入模块的时候,通过 __all__
变量可以指定可导入的部分。如下实例:
在 hello.py 中编写以下代码:
#hello.py模块代码
def printInfo():
print('hello, world!')
def printSum(x,y):
print(x+y)
#内部函数,不期望被导出
def __fun__():
print("just for test!")
#通过__all__变量定义模块的公有接口
__all__=['printInfo','printSum']
在 test.py 中编写以下代码:
#test.py模块中的代码
#导入hello模块
from hello import *
#调用hello模块中的函数
printInfo()
printSum(123,12)
__fun__()
运行 test.py 模块,执行结果:
hello, world!
135
Traceback (most recent call last):
File "D:\Users\xxx\testI\test.py", line 11, in <module>
__fun__()
NameError: name '__fun__' is not defined
出现了报错,由于 hello 模块的公有接口定义变量 __all__
中没有包含 __fun__()
函数,使用 from hello import*
语句无法将其导入,因而报错。
用 help 获取帮助
对于一个的模块,可以借助标准函数 help 获取其信息,以 math 模块为例:
import math
#获取math模块的信息
help(math)
执行结果:
NAME
math
DESCRIPTION
This module is always available. It provides access to the
mathematical functions defined by the C standard.
FUNCTIONS
acos(...)
acos(x)
Return the arc cosine (measured in radians) of x.
……
文档 __doc__
文档用于存放一个模块的描述信息,在开发中遇到不清楚的问题,可以通过文档获取必要信息,阅读文档获取第一手资料是一个开发人员必备的技能。如下例子:
import sys
#获取sys模块的文档
print(sys.__doc__)
执行结果:
This module provides access to some objects used or maintained by the
interpreter and to functions that interact strongly with the interpreter.
Dynamic objects:
argv -- command line arguments; argv[0] is the script pathname if known
path -- module search path; path[0] is the script directory, else ''
modules -- dictionary of loaded modules
……
标准库
Python 安装包提供的模块总称为标准库,在前面的举例中已经使用过一些标准库中的模块,如 math、sys、os。Python 的标准库非常强大,不可能一一列举,在此,介绍几个常用的模块。
- sys 模块:通过它可以访问多个与 Python 解释器联系紧密的函数和变量;
- os:通过它可以访问多个与操作系统联系紧密的函数和变量;
- fileinput:通过它可以轻松遍历多个文件和流中的所有行;
- time:通过它可以获取当前时间,并可以进行时间日期操作及格式化;
- random:通过它可以产生随机数,从序列中选取随机元素及打乱列表元素;
- re:通过它可以轻松使用正则表达式。