在程序运行过程当中,总会遇到各类各样的错误。有的错误是程序编写有问题形成的,好比原本应该输出整数结果输出了字符串,有的错误是用户输入形成的,好比让用户输入email地址,结果获得一个空字符串,这种错误能够经过检查用户输入来作相应的处理。还有一类错误是彻底没法在程序运行过程当中预测的,好比写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络忽然断掉了。这类错误也称为异常,在程序中一般是必须处理的,不然,程序会由于各类问题终止并退出。linux
在Python中产生异常主要有两种方式:解释器出触发和手动触发编程
当异常被触发后,触发异常的代码段的后续代码将不会被继续执行。若是存在于顶层命名空间中,可能还会致使整个程序退出。json
什么状况下解释器会触发异常?举个例子:网络
print('hello world) print(1/0)
当解释器一行一行解释时,因为print函数少些一个引号,不符合Python语法规范,因此会提示SyntaxError,严格来讲的话,语法错误不算一种异常,在解释阶段就没法经过,因此没办法进行捕获,其余的还好比IndentationError,由于是SyntaxError的子类, 而print(1/0)的除数不能为零,符合Python语法规范,只是在代码运行时触发了ZeroDivisionError异常,这种在运行时触发的异常,咱们都是能够捕获的。编程语言
在代码中,使用raise
关键字,能够手动触发异常,当检测某些数据类型,不符合预期时,就能够手动的触发,它的格式以下:模块化
raise 异常类型 raise 异常类型() raise 自定义异常 raise 自定义异常()
手动触发异常:函数
def add(x,y): if isinstance(x, int) or isinstance(y, int): raise TypeError return x + y
函数add期待两个类型为int的参数,若是咱们传递'a','1'时,因为Python语言的特性,预期的结果就可能会不一样,若是后续代码依赖该函数的返回值,那么就可能会引起后续代码的异常报错,不利于排查。
raise关键字能够触发异常类,也能够触发异常类实例,其实当咱们触发异常类时,实际上raise会帮咱们实例化,而后触发。异常类也能够传参,其本质上,能够理解为:测试
raise Exception == raise Exception() class Exception: def __init__(self, message='Exception', code=200): self.message = message self.code = code ......
不传递参数时,使用默认属性初始化,传递参数时,则参数进行实例初始化赋值。ui
Python内置了大量的异常类,用于标识不一样的异常以及分类。以下
BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- ImportError | +-- ModuleNotFoundError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- OSError | +-- BlockingIOError | +-- ChildProcessError | +-- ConnectionError | | +-- BrokenPipeError | | +-- ConnectionAbortedError | | +-- ConnectionRefusedError | | +-- ConnectionResetError | +-- FileExistsError | +-- FileNotFoundError | +-- InterruptedError | +-- IsADirectoryError | +-- NotADirectoryError | +-- PermissionError | +-- ProcessLookupError | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- ResourceWarning
不一样的层级表示继承关系,经常使用的错误及其含义以下:
AttributeError # 试图访问一个对象没有的属性时 IOError # 输入/输出异常;基本上是没法打开文件 ImportError # 没法引入模块或包;基本上是路径问题或名称错误 SyntaxError # 语法错误 IndentationError # 代码没有正确对齐(SyntaxError的子类) IndexError # 下标索引超出序列边界,好比当x只有三个元素,却试图访问x[5] KeyError # 试图访问字典里不存在的键 KeyboardInterrupt # Ctrl+C被按下 NameError # 使用一个还未被赋予对象的变量 TypeError # 传入对象类型与要求的不符合 UnboundLocalError # 试图访问一个还未被设置的局部变量,基本上是因为另有一个同名的全局变量,致使你觉得正在访问它 ValueError # 传入一个调用者不指望的值,即便值的类型是正确的,好比列表a=[1, 2, 3],而你执行了a.remove(4) SystemExit # sys.exit()函数触发的异常,异常被解释器发现时,会退出执行 ArithmetieError # 全部算数引起的异常 LookupError # 使用映射的键或序列的索引无效的异常
Python提供的大部分运行时的错误异常,都继承自Exception类,因此咱们自定义的异常类,也要从Exception类继承(固然也能够从BaseException继承,但不建议。)
自定义的异常类须要从Exception进行继承
class MyException(Exception): def __init__(self,message='Not Exception',code=200): super().__init__(message,code) # 就等于 # class MyException(Exception): # pass try: raise MyException('from MyException') raise MyException except: print('没有异常')
因为异常有默认值,因此传递不传递参数均可
所谓异常处理就是:出现异常,捕获并处理异常。一个健壮的程序应该尽量的的避免错误,尽量的捕捉错误,处理各类异常。Python内置了一套异常处理机制,来帮助咱们进行错误处理。其完整格式以下:
try: 代码段1 # 检测执行的代码段 except: 代码段2 # try代码段产生异常后,被捕捉到后执行的代码段 else: 代码段3 # try代码段中没有异常要执行的代码段(非必须) finally: 代码段4 # try代码段中无论有没有异常,都要执行的代码段(非必须)
其中except语句能够有多个,捕捉不一样类型的异常并进行相应的处理,而且还可利用as语句存储异常,因此except的扩展格式:
except: 表示遇到异常就捕捉。 except Exception: 表示只捕捉Exception类的异常,而Exception类包含了Python内置的全部异常,因此功能相似于捕捉全部。 except IndexError as e: 捕捉IndexError异常,并交给变量e存储。
须要注意的是: except子句必须和try在同一层,搭配使用,不能单独使用except
。下面来看一个小例子:
try: a=[1,2,3] a[4] except: print('一切正常',e) else: print('真的是一切正常') finally: print('我是 finally')
分析:
因为列表a只有3个元素,取列表第四个元素,会报异常(IndexError: list index out of range,从pycharm的console窗口就能看到),异常被except捕捉,因此会打印,一切正常,并打印错误信息。因为产生了异常不会执行else语句,最后执行finally语句。
实际上咱们一段代码可能会有不一样类型的错误,而咱们须要针对不一样类型的错误,作不一样的操做,因此这个时候就会用到多except语句。
try: a = [1,2,3,4] a[7] except IndexError as e: print('索引错误',e) except KeyError as e: print('key错误',e ) except: print('发生异常了')
分析:
异常被IndexError捕获,因此会打印索引错误。
多条except语句,优先级从上到下,和if条件的优先级是相同的,即若是捕获到一个,就不继续执行except语句了。利用多except语句,来对不一样的异常进行处理,可使咱们的程序更健壮。
先来看下面的例子:
def test(x, y): try: print('from Test') return x / y finally: return 200 a = test(1,0) print(a) # from Test # 200
分析:
在test语句中因为除数为0,因此会触发ZeroDivisionError异常,因为finally是最后被执行的,尚未上报呢,就return了,这个时候异常就被压制了。
函数的返回值取决于最后一个执行的return语句,finally是最后被执行的语句。因此在finally语句中return是一个不明智的决定,除非你真的须要它。
当异常没有被处理时,就会向上层逐级上报。
def foo1(): foo2() def foo2(): foo3() def foo3(): raise Exception foo1() # Traceback (most recent call last): # File "E:/Python - base - code/chapter08_OOP/module_and_package.py", line 15, in <module> # foo1() # File "E:/Python - base - code/chapter08_OOP/module_and_package.py", line 7, in foo1 # foo2() # File "E:/Python - base - code/chapter08_OOP/module_and_package.py", line 10, in foo2 # foo3() # File "E:/Python - base - code/chapter08_OOP/module_and_package.py", line 13, in foo3 # raise Exception # Exception
异常在foo3中产生,上报到foo2中,foo2没有处理,上报到foo1中,foo1没有处理,继续上报,若是到了外层尚未被处理,就会中断异常所在的线程的执行。
try ... except 能够被嵌套使用
咱们能够在可能产生异常地方当即捕获,或者在边界捕获。当即捕获很好理解,那什么是边界捕获呢?
try的工做原理:
通常来讲,编程语言中,库,包,模块是同一种概念、是代码的组织方式。Python中只有一种模块对象类型,可是为了模块化组织模块的便利,提供了'包'的概念。
Pythong主要提供了两类导入模块的方法。它们是import和from ... import。
语句 | 含义 |
---|---|
import n1,n2 | 彻底导入 |
import x as x | 模块别名彻底导入 |
导入过程:
import os print(list(filter(lambda name: not name.startswith('_'),dir()))) # 去掉_开头的方法 # ['os']
dir()返回当前名称空间内的名称。
(1) import functools as ft print(list(filter(lambda name:not name.startswith('_'),dir()))) # ['ft'] (2) import os print(os) # <module 'os' from 'D:\\Python3.6.6\\lib\\os.py'> print(os.path) # <module 'ntpath' from 'D:\\Python3.6.6\\lib\\ntpath.py'> print(list(filter(lambda name: not name.startswith('_'),dir()))) # ['os']
当咱们使用as语句时,那么当前名称空间会仅存在于一个as后的别名,来指向模块了。os.path在打印时也是一个模块,那么咱们可否只导入os模块下的path模块呢?
import os.path print(list(filter(lambda name: not name.startswith('_'),dir()))) # ['os']
并无导入os.path模块,只有os模块,这是为何呢?
若是使用了as,as后的名称直接绑定到导入的模块对象上,并将该名称加入到本地命名空间中。
import 后面只能是包名,或者模块名
语句 | 含义 |
---|---|
from xx import xx | 部分导入 |
from xx import xx as xx | 别名部分导入 |
导入过程:
from os import path
print(list(filter(lambda name: not name.startswith('_'),dir()))) # ['path']
from os import path as osp
print(list(filter(lambda name: not name.startswith('_'),dir()))) # ['osp', 'path']
```
from 后面只能是模块或者包名,import后面能够是包、模块、类、函数、甚至是某个变量
一个py文件就是一个模块。先不考虑模块查找顺序,看下面例子:
# 目录结构 # Learn_Python_fromn_Zero # |--my_module.py # |--my_script.py (1) my_module.py class MyClass: def __init__(self, name): self.name = name def print(self): print('{} say hello'.format(self.name)) def my_func(context): print(context) (2) my_script.py import my_module daxin = my_module.MyClass('daxin') daxin.print() # daxin say hello
固然也能够经过from方式只导入某个类
from my_module import MyClass daxin = MyClass('daxin') daxin.print() # daxin say hello
须要注意的是,自定义模块的名称要遵循必定的规范:
sys.path是一个列表,里面存放的就是模块搜索的路径
import sys print(*sys.path,sep='\n') # E:\Learn_Python_from_Zero\chapter08\_Package_and_module # E:\Learn_Python_from_Zero # D:\Python3.6.6\python36.zip # D:\Python3.6.6\DLLs # D:\Python3.6.6\lib # D:\Python3.6.6 # D:\Python3.6.6\lib\site-packages
上面的结果就是我当前环境下Python模块的路径搜索顺序,当加载一个模块的时候,须要从这些路径中从前到后依次查找,但并不搜索这些目录的子目录,搜索到模块就加载,搜索不到就抛异常。
大体的路径顺序为:
程序主目录
,运行程序的主程序所在的目录PYTHONPATH目录
,环境变量PYTHONPATH设置的目录,也是搜索模块哦路径标准库目录
,Python自带的库模块所在的目录
sys.path是一个列表,那么就意味着能够进行增删改查,不过通常咱们不会操做这个路径,除非你真的须要。
咱们说模块的导入就是一个加载以及关联到标识符存入本地命名空间的过程,若是我要屡次导入相同的模块,是否是要屡次加载,屡次从新存入本地命名空间呢,写个代码测试一下
import os print(id(os)) import os as daxin print(id(daxin)) import os print(id(os)) # 2300567923272 # 2300567923272 # 2300567923272
三次导入的模块的内存地址是相同的,说明只会导入一次,这是为何呢?这是由于在Python中,全部的加载的模块都会记录在sys.modules中,它里面存放的是已经加载过的全部模块的字典,导入模块就和它有关:
每一个模块都会定义一个__name__特殊变量来存储当前模块的名称,若是不指定,默认为源码文件名,若是是包则有限定名(包名.文件名)。
解释器初始化的时候,会初始化sys.modules字典,用来保存已加载的模块,加载builtins(全局函数、常量)模块,__main__模块,sys模块,以及初始化模块搜索路径sys.path
当从标准输入(命令行方式敲代码)、脚本(在命令行运行)或者交互式读取的时候,会将模块的__name__设置为__main__,模块的顶层代码就在__main__这个做用域中执行。若是是import导入的,其__name__默认就是模块的名称。
(1) mymodule.py print(__name__) (2) myscript.py import mymodulel.py
有如上两个py文件存在于同一层级下:
不少时候,咱们写的函数,类须要在其余地方调用,导入时会自动执行模块,若是存在不少print语句时,就会直接在导入的地方进行输出,不便于调用。因此通常咱们都使用下面结构:
if __name__ == '__main__': pass
他的好处是:
属性 | 含义 |
---|---|
__file__ | 当前文件的绝对路径(字符串格式) |
__cached__ | 编译后的字节码文件路径(字符串格式) |
__sepc__ | 显示模块的规范 |
__name__ | 模块名 |
__package__ | 当模块是包时,同__name__,不然能够设置为顶级模块的空字符串。 |
Python中把包理解为一个特殊的模块,在文件系统上的体现就是一个目录。用于组织多个模块或者其余更多的包。主要有两种格式:
目录下包含一个__init__.py文件
新版本的Python直接认为一个目录就是一个包,但老版本的包,下面须要存在一个__init__.py文件,因此规范化起见,建议建立这个文件。
__init__.py文件的做用:
在linux下,一切皆文件,那么一个目录也是一个文件,Python认为包文件自己也能够有必定的属性或者其余信息,因此在包下用init.py标识这个包自身的属性信息或者其余代码。包在导入时,它下面的__init__.py 就会被执行
包目录下的其余py文件、子目录都被称为它的子模块。
# 目录结构: ---- mypackage |--- __init__.py |--- hello |--- __init__.py |--- mymodule.py ---- myscript.py # myscript.py import mypackage print(mypackage.hello.mymodule) # ? 能够执行吗
不能够执行,咱们说可否执行主要看sys.modules中是否加载,以及是否关联到本地命名空间中去
import mypackage print(dir()) # ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'mypackage'] print(dir(mypackage)) # ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
从命名空间中,咱们能够看到,mypackage包下并无hello这个包(都没有加载到sys.modules中去),因此说直接经过import mypackage是没法导入它下面的子包以及子模块的。
import mypackage.hello.mymodule import sys print(sys.modules.keys()) # [ ..., 'mypackage', 'mypackage.hello', 'mypackage.hello.mymodule'] print(dir()) # [ ... 'mypackage', 'sys'] mypackage.hello.mymodule.echo('hello world')
从命名空间中,咱们看到只有顶级包名加入了本地命名空间,可是咱们导入的hello,mymodule已经加载到了sys.modules中,这样咱们就可使用mypackage调用了。
from json import encoder json.dump可使用吗? # 没法使用,由于虽然加载了json模块,可是仅仅把encoder导入了当前名称空间 import json.encoder json.dump可使用吗? # 可使用,由于eocoder是一个模块,这种导入当时,只会将顶层json模块加入到本地名称空间中,因此能够经过json执行dump函数。
绝对导入:在import语句或者from语句中,导入模块。模块名称最前面不是以.点开头的。绝对导入老是去模块搜索路径中寻找(也会查看是否已经加载)
相对导入:只能在包内使用,且只能用在from语句中,使用.点号,表示当前目录内。使用..表示上一级目录。
不要在顶层模块中使用相对导入。
--- package1 | --- m1.py | --- m2.py --- m3.py # m2.py from . import m1 # m3.py from package1 import m2
直接运行m2.py时,是没法导入m1的。而,经过m3,导入m2,是正常的。这是由于:一旦一个模块中使用了相对导入,就不能够做为主模块运行了
。
使用相对导入的模块就是为了内部相互引用资源的,不是为了直接运行的,对于包来讲,正确的使用方式仍是在顶级模块使用这些包。
模块中的变量、类、函数等等均可以称做模块的属性,在导入的时候,均可以被直接导入到本地命名,只要它们符合一个合法的标识符便可(包名,也一样适用)
# mymodule.py _a = 123 __x = 456 y = 789 class A: pass def hello():pass # my_script.py import mymodule print(dir(mymodule)) # ['A','__x', '_a', 'hello', 'y']
也就是说模块内没有私有变量,在模块定义中不作特殊处理(类的私有变量会被更名)
使用from xxx import * 导入时,xxx模块中的私有属性以及保护属性将不会被导入。
# mymodule.py _a = 123 __x = 456 y = 789 class A: pass def hello():pass # my_script.py from mymodule import * print(dir()) # ['A','__x', '_a', 'hello', 'y']
通常写在文件的前几行,用于表示当from xxx import * 导入本模块时,哪些属性能够被别人导入,它的值是一个列表.
# mymodule.py __all__ = ['_a','y] _a = 123 __x = 456 y = 789 class A: pass def hello():pass # my_script.py from mymodule import * print(dir()) # ['_a', 'y']
因此使用from xxx import * 导入时: