Python 错误和异常

异常Exception

错误Error:又称解析错误,是可以避免的
异常Exception:在没有出现上面错误的前提下,语句和语法都是正确的,本身是意外情况,异常是不可避免的

In [1]: "asd" = 100
  File "", line 1
    "asd" = 100
# 这是错误               ^
SyntaxError: can't assign to literal

In [2]: with open("test") as f:
   ...:     f.read()
   ...:     
Traceback (most recent call last):

  File "", line 1, in <module>
    with open("test") as f:
# 这是异常
FileNotFoundError: [Errno 2] No such file or directory: 'test'

在高级编程语言中,一般都有错误和异常的概念,异常可以捕获,错误不能被捕获
尽可能的避免错误
尽可能的捕获、处理异常

产生异常

产生

  • raise语句显式的抛出异常
  • Python解释器自己检测到异常并引发它
In [3]: print(1/0)  # ZeroDivisionError  

In [4]: raise TimeoutError # raise抛出的异常

异常的捕获

try:
    待捕获异常的代码块
except [异常类型]: 
    异常的处理代码块
In [5]: def fn():
   ...:     try:
   ...:         print(1/0)
   ...:     except ZeroDivisionError: # 指定捕获的异常类型
   ...:         print("ZeroDivisionError!")
   ...:     
   ...:     print("catch it!")
   ...:     

In [6]: fn()
ZeroDivisionError!
catch it!

注意:如果写明异常类型,异常类型要与上面发生的异常匹配,否则捕捉不到

In [7]: def fn():
   ...:     try:
   ...:         print(1/0)
                print("hi") # 因为上面发生了异常,这一句并不会被执行
   ...:     except FileNotFoundError: # 发生的异常和指定的异常类型不匹配
   ...:         print("ZeroDivisionError!")
   ...:     
   ...:     print("catch it!")
   ...: 

In [8]: fn()
Traceback (most recent call last):

  File "", line 1, in <module>
    fn()

  File "", line 3, in fn
    print(1/0)

ZeroDivisionError: division by zero

异常类及继承层次

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

各种异常类型关系

BaseException及子类

1.SystemExit

sys.exit()引发的异常,异常不捕获处理,直接交给解释器,解释器退出
如果except该句捕获了该异常,则继续往后执行,如果没有捕获,解释器退出

In [10]: try:
    ...:     sys.exit(1)
    ...: except SystemExit:
    ...:     print("SysExit")
    ...: print("catch it!")
SysExit
catch it!

2.KeynoardInterrupt

对应的中断行为Ctrl+C

注:在解释器环境下使用这种捕获,普通ide环境热键冲突

In [12]: import time

In [13]: try:
    ...:     while True:
    ...:         time.sleep(1)
    ...:         print("===")
    ...: except KeyboardInterrupt:
    ...:     print("Ctrl + C")
    ...: print("==end==")
===
===
===
===
Ctrl + C
==end==

3.Exception及子类

Exception是所有内建的、非系统退出的异常的基类

注:python将有些语法错误归类到Exception下面,但这种错误是不可捕获的

  • ArithmeticError:所有算数计算引发的异常,子类有:

    • FloatPointError
    • OverflowError
    • ZeroDivisionEroor
  • LookupError : 使用映射的键或者序列的索引无效时引发的异常的基类

    • IndexError
    • KeyError
  • 自定义异常类 :

class MyException(Exception):
    pass

In [35]: class M(ArithmeticError):
    ...:     pass
    ...:     

In [36]: try:
    ...:     raise M()
    ...: except M:
    ...:     print("catch it!")
    ...:     
catch it!

这里有个坑,就是自定义的异常只能手动raise抛出,解释器不知道你定义的异常类是什么鬼,即使你是从别的异常类继承下来的类

多种捕获

except可以指定捕获的类型,捕获多种异常
多个except即可,但是之后最多匹配一个异常

In [38]: try:
    ...:     1/0
    ...:     raise FileNotFoundError
    ...:     open("test")
    ...:     sys.exit(2)
    ...: except ZeroDivisionError:
    ...:     print("catch 1")
    ...: except FileNotFoundError:
    ...:     print("catch 2")
    ...: except Exception:
    ...:     print("catch 3")
    ...: except:
    ...:     print("catch 4")
    ...: print(5)
catch 1
5

捕获规则:
①从上到下依次比较,如果匹配,则执行匹配的except语句块
②如果一个except语句捕获,其他except语句不会再次捕获了
③如果没有任何一个except语句捕获这异常,则该异常向外抛出

捕获的原则:
从小到大,从具体到宽泛

as字句

except ... as ...

raise抛出的异常,应该都是异常类的实例
使用exception as e:查看e以及类型查看是否按要求捕获到

In [40]: try:
    ...:     raise 1
    ...: except Exception as e:
    ...:     print("catch it")
    ...:     print(e)
    ...:     
catch it
exceptions must derive from BaseException # 

rasie语句

注意区分下面两种情况

In [50]: class M(Exception):
    ...:     def __init__(self, code, message):
    ...:         self.code = code
    ...:         self.message = message
    ...:         
    
# 1
In [51]: try:
    ...:     raise M # 抛出的是类,但是这个类被解释器无参实例化,所以报参数错误
    ...: except M as e:
    ...:     print("catch it!")
    ...: except Exception as e:
    ...:     print("111")
    ...:     print(e)
    ...: print("===")
111
__init__() missing 2 required positional arguments: 'code' and 'message'
===

# 2
In [52]: try:
    ...:     raise M(200,"ok")  # 抛出的是类的实例化,所以被except M as e捕获
    ...: except M as e:
    ...:     print("catch it!")
    ...: except Exception as e:
    ...:     print("111")
    ...:     print(e)
    ...: print("===")
catch it!
===
  • rasie后要求应该是BaseException类的子类或者实例化,如果是类,将被无参实例化
  • raise后什么都没有,表示抛出最近一个被激活的异常,如果没有被激活的异常,则抛类型异常,这种方式很少用
In [53]: try:
    ...:     raise FileNotFoundError
    ...: except:
    ...:     raise # 抛出上面最近的一个被激活的异常FileNotFoundError
    ...: finally:
    ...:     print("finally")
    ...: print("==end==")
finally
Traceback (most recent call last):

  File "", line 2, in <module>
    raise FileNotFoundError

FileNotFoundError

finally

即最后一定要执行的语句块
可以在函数中使用finally,并且函数的返回值取决于最后一个执行的return语句
函数遇到真正的最后一个可以被执行的return就不会执行了

In [57]: def f():
    ...:     # return 1
    ...:     try:
    ...:         return 2
    ...:     finally: # 一定会被执行的语句块
    ...:         return 3
    ...:     return 4
    ...:     

In [58]: f()
Out[58]: 3

注:如果except没有捕获到异常,finally结束后的后面的语句不会执行,最后会抛出一个异常

异常的传递

异常总是向外层抛出,一直抛出到被捕获,如果到了最外层还没被捕获,就会中断异常所在的线程的执行

def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    bar('0')

main()

Traceback (most recent call last):
  File "err.py", line 11, in <module>
    main()
  File "err.py", line 9, in main
    bar('0')
  File "err.py", line 6, in bar
    return foo(s) * 2
  File "err.py", line 3, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero

出错并不可怕,可怕的是不知道哪里出错了。解读错误信息是定位错误的关键。我们从上往下可以看到整个错误的调用函数链。
最后我们定位到错误的源头:foo函数中的10/0

记录错误

如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。

Python内置的logging模块可以非常容易地记录错误信息:

import logging

def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)

main()
print('END')

同样是出错,但程序打印完错误信息后会继续执行,并正常退出:

ERROR:root:division by zero
Traceback (most recent call last):
  File "err_logging.py", line 13, in main
    bar('0')
  File "err_logging.py", line 9, in bar
    return foo(s) * 2
  File "err_logging.py", line 6, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero
END

通过配置,logging还可以把错误记录到日志文件里,方便事后排查

这里转自廖雪峰博客
Python关于模块 logging — Python 的日志记录工具
后面详细介绍logging日志记录工具

try嵌套

内部捕获不到异常,回向外层传递异常
但是如果内部有finally且其中有return、break语句,则异常就不会继续往外抛出,异常就会被丢弃了

In [2]: def foo():
   ...:     try:
   ...:         1/0
   ...:     except KeyError as e: # 异常不匹配,所以捕捉不到异常
   ...:         print(e)
   ...:     finally:
   ...:         print("foo!")
   ...:         return 2  # 异常在这里被丢弃
   ...:         
   ...: try:
   ...:     foo() 
   ...: except:
   ...:     print("catch") 
   ...: else: # 因为上面的foo()函数并不会抛出异常,所以else语句正常执行
   ...:     print("good else")
   ...: finally: # finally一定执行
   ...:     print("end")
   ...:     
foo!
good else
end

异常的捕获时机

1.立即捕获,需要立即返回一个明确的结果

比如遇到异常就 return 0

In [3]: def parse_int(s):
   ...:     try:
   ...:         return int(s)
   ...:     except:
   ...:         return 0 # 遇到异常就返回0
   ...:         

In [4]: parse_int([])
Out[4]: 0

2.边界捕获

  • 封装产生了边界
  • 例如open函数,出现的异常交给调用处理,文件存在了,就不用了创建了,看是否修改还是删除
  • 根据实际环境选择捕获时机

else字句

else表示没有任何异常发生,则执行

In [9]: try:
   ...:     0/1
   ...:     print("===")
   ...: except:
   ...:     print("123")
   ...: else: # 上面没抛异常,这里会被执行
   ...:     print("success")
   ...: finally:
   ...:     print("finally")
   ...:     
===
success
finally

总结

try:
    <语句>                      # 运行时的代码
except [异常类]:
    <语句>
    raise                       # 抛出上面收到的异常
except [异常类] as <变量名>:    # 捕获某种异常并获得对象e
    <语句>
else:                           # 如果没有任何异常发生则执行
    <语句>
finally:
    <语句>                      # 退出try时总会执行 

try的工作原理

1.如果try语句执行时发生异常,搜索except字句,并执行第一个匹配该异常的except字句
2.如果try语句中语句执行时发生异常,却没有匹配的except字句,异常将被递交到外层的try,如果外层不处理这个异常,异常将继续将外层传递,如果到了最外层还没被处理,就终止异常所在的线程
3.如果try执行时没发生异常,如有else字句,那么这个else字句可以被执行
4.无论try是否发生异常,finally都会被执行

python官方文档"错误和异常"

你可能感兴趣的:(Python函数以及面向对象,异常处理,python,python3)