zoukankan      html  css  js  c++  java
  • python: 错误处理try详解 ,traceback调用栈, 调试(logging)

    摘录:https://www.liaoxuefeng.com/wiki/1016959663602400/1017598873256736

    •  错误处理
    •  调试
    •  新增:2020-01-19 增加关于文件读取的方法tell(), seek()等知识。

    错误处理

    高级语言都会使用内置的一套try...except...finally...的错误处理机制, 可以更高效的处理错误,

    无需程序员自己写错误处理的代码。

    try

    try:
        print('try...')
        r = 10 / int('2')
        print('result:', r)
    except ValueError as e:
        print('ValueError:', e)
    except ZeroDivisionError as e:
        print('ZeroDivisionError:', e)
    else:
        print('no error!')
    finally:
        print('finally...')
    print('END')
    • 如果有错误,根据发生不同类型的错误,使用不同的except处理。
      • int('a')会出发 ValueError
      • 10/0会触发ZeroDivisionError。
      • 如果没有找到expect匹配的异常,则将异常传递到外部的try语句中。会显示traceback
    • 否则代表没有错误,则执行else。
    • 无论是否发生异常,最后都要执行finally

    常见的错误类型和继承关系

    看文档Built-in Exceptions

    https://docs.python.org/3/library/exceptions.html#exception-hierarchy

    try的好处

    可以处理try子句中调用(间接调用)的函数内部发生的异常,即跨多层调用。

    函数main()调用bar(), bar调用foo(), 只要期间发生错误,try就会处理。

    def foo(s):
        return 10 / int(s)
    
    def bar(s):
        return foo(s) * 2
    
    def main():
        try:
            bar('0')
        except Exception as e:
            print('Error:', e)
        finally:
            print('finally...')

    调用栈

    如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序终止。

    错误信息是Traceback (most recent call last)...。它是一个错误路径。可以在最后查看错误原因,定位错误位置。

    例子:

    import sys
    
    try:
        f = open('example.log', 'r+')
        s = f.readline()
        i = int(s.stripd())
    except OSError as err:
        print("OS error: {}".format(err))
    except ValueError:
        print("Could not convert datga to a integer")
    except:
        print("Unexpected error", sys.exc_info()[0])
        raise
    else:
        print(i)
    
    # OSError类, 就是IOError的别名。输入输出错误类.会升起和系统错误代码香港的错误。包括很多子类
    
    # 最后的excepte没有指定异常类,当作通配符用。
    # sys.exc_info会返回,当前正在处理的exception的信息:
    # 返回的值是一个tuple:包括3个value。(type, value, traceback), 如果没有对应的值返回None。
    #example.log
    213
    1

    解释:

    • f = open(), 使用内建方法open。open其实是io模块的方法。
    • s = f.readline(), 会把example.log文件的第一行代码返回给变量s。
    • s.stripd(),这是❌的拼写,多写了一个字面d,系统有内置函数strip(),所以要去掉字母d,否则会执行exept子句。
    • 代码最后一个except子句没有异常类,所以向上级抛错误。
    • sys.exc_info():这个方法是sys模块的方法。用法见注释。

    本例子返回的是:

    Unexpected error <class 'AttributeError'>    
    Traceback (most recent call last):             
      File "linshi.py", line 6, in <module>
        i = int(s.stripd())
    AttributeError: 'str' object has no attribute 'stripd'

    except子句的as

    except Exception as inst:

    as 后面的inst变量和产生的异常类的实例绑定,如果异常实例有参数,使用inst.args查看。

    因为异常类BaseException类定义了object.__str__(self)方法, 这个方法返回对象自身的字符串格式,所以可以直接使用inst,查看错误。

    例子:

    def this_fails():
        x = 1/0
    
    try:
        this_fails()
    except ZeroDivisionError as err:
        print("Handing run-time error:", err.args)
        print("Handing run-time error:", err)
    #返回:
    Handing run-time error: ('division by zero',)
    Handing run-time error: division by zero

      

    logging模块

    可以把错误信息记录到日志。并让程序继续执行。

    # err_logging.py
    
    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')

    抛出异常 raise语句

    raise(参数), 参数是异常类或者它的实例。如果参数是异常类,它会通过调用构造函数来暗中/隐式实例化。

    内置函数有各种类型的错误。也可以自己编写函数,然后抛出错误。

    # err_raise.py
    class FooError(ValueError):
        pass
    
    def foo(s):
        n = int(s)
        if n==0:
            raise FooError('invalid value: %s' % s)
        return 10 / n
    
    foo('0')
    • 编写一个错误类FooError。
    • raise语句,生成FooError的实例。

    尽量使用内置的函数。如ValueError, TypeError

    最常用的错误处理方式:try...except...并调用一个单独的raise

    目的:

    • 用except捕获指定❌
    • 然后,用raise语句进行trackback。

    下面代码:注释掉了raise,所以最后结果不会显示Traceback (most recent call last)的信息。

    from functools import reduce
    
    def str2num(s):
        return int(s)    #改为float(s)即可纠正错误
    
    def calc(exp):
        ss = exp.split('+')
        ns = map(str2num, ss)
        return reduce(lambda acc, x: acc + x, ns)  #functools的方法reduce(function, iterable)
    
    def main():
      try:
        r = calc('99 + 88 + 7.6')
        print('99 + 88 + 7.6 =', r)
      except ValueError as e:
        print(">>>%s" % e)
        # raise
    main()
    • 加上单独的raise的作用,就是把当前错误原样抛出,可进行后续的追踪。
    • 由于当前函数不知道应该怎么处理该错误,所以,继续往上抛错误,让顶层调用者去处理。
    • 如果raise带了不同的异常类实例的参数,,可以把一种类型的错误转化为另一种类型: 
    try:
        10 / 0
    except ZeroDivisionError:
        raise ValueError('input error!')

    finally的作用

    finally是try语句的可选子句,它不论是否产生异常都会执行。用于定义清理操作。

    但有几点特殊情况,执行的时间不一样。具体可见文档: 8.6. 定义清理操作


    调试

    第一种方法简单直接粗暴有效,就是用print()把可能有问题的变量打印出来看看。

    第二种:print()但看完还要删除,因此可以用assert。

    不用了,可以在启动时,带上参数-O关掉assert的功能。

    #err.py
    def foo(s):
        n = int(s)
        assert n != 0, 'n is zero!'
        return 10 / n
    
    def main():
        foo('0')
    main()
    $ python -O err.py

    注意是大写的字母O,

    第三种logging。

    import logging
    logging.basicConfig(level=logging.INFO)  #进行配置,logging级别,默认是WARNING,所以INFO,DEBUG级别的不会被追踪
    
    s = '0'
    n = int(s)
    logging.info('n= %d' % n)
    print(10/0)

    这就是logging的好处,它允许你指定记录信息的级别。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。

    级别

    何时使用

    DEBUG

    细节信息,仅当诊断问题时适用。

    INFO

    确认程序按预期运行

    WARNING

    表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行

    ERROR

    由于严重的问题,程序的某些功能已经不能正常执行

    CRITICAL

    严重的错误,表明程序已不能继续执行

    默认的级别是``WARNING``,意味着只会追踪该级别及以上的事件,除非更改日志配置。

    追踪的事件可以:

    • 简单的:输出到控制台
    • 复杂的:写入磁盘文件。

     

    记录日志到磁盘文件:

    使用basicConfig()的配置函数,更改上面的代码:

    logging.basicConfig(filename='example.log',level=logging.DEBUG)

    这样,会在当前文件夹下生成一个example.log文件,用于记录日志信息。就是logging.info()输出的信息。 

    在消息中显示时间/日期

    日志很重要的是,可以在之后查看,所以要加上日期时间:

    logging.basicConfig(filename='example.log',format="%(levelname)s: %(asctime)s %(message)s",level=logging.INFO)  #进行配置

    加上参数:format="%(levelname)s: %(asctime)s %(message)s"

    会在日志显示:

    WARNING: 2019-11-16 19:29:46,496 n= 0

    是否要深入学习?

    如果日志需求很简单,上面的就足够了,否则可以看进阶日志教程和操作手册。

    第四种pdb, Python自带的调试器

    $ python -m pdb err.py
    > /Users/michael/Github/learn-python3/samples/debug/err.py(2)<module>()
    -> s = '0'

    带上参数-m pdb

    运行时,c是continue下一个块, n是next即下一行,q是退出。

    如果想要设置一个中断点,在代码中加上pdb.set_trace()方法即可,运行是到这行代码会暂停并进入调试程序。

    ⚠️类似Ruby/Rails的byebug。中断点是byebug, 启动脚本: byebug xxx.rb

    IDE: 集成开发环境

    如果要比较爽地设置断点、单步执行,就需要一个支持调试功能的IDE。目前比较好的Python IDE有:

    Visual Studio Code:https://code.visualstudio.com/,需要安装Python插件。

    PyCharm:http://www.jetbrains.com/pycharm/

    另外,Eclipse加上pydev插件也可以调试Python程序。

    虽然用IDE调试起来比较方便,但是最后你会发现,logging才是终极武器。


    文件的定位读写

    f = open("123.txt", "w+")
    f.write("hello world")
    content = f.read()
    print(content)  # 为什么打印不出内容?

    这是因为有一个关于打印指针的概念。

    第3行的变量content其实是""空字符串,因为文件中的指针当前指向的是文本的最后。

    所以,如果希望打印第2行输入的字符,需要调整文件指针的位置。这里涉及2个方法:

    • tell(),判断当前指针的位置
    • seek(offset, from) 移动指针

    seek(offset, from)有2个参数

    • offset:偏移量。正整数向右偏移,负数向左偏移。
    • from:方向
      • 0:表示文件开头
      • 1:表示指针当前的位置
      • 2:表示文件末尾

     

  • 相关阅读:
    决策模型(一):不确定型决策法
    Redis系列(二):Redis的数据类型及命令操作
    Redis系列(一):Redis的简介与安装
    java中传值方式的个人理解
    理解主从设备模式(Master-Slave)
    基于ubuntu16.04部署IBM开源区块链项目-弹珠资产管理(Marbles)
    基于ubuntu16.04快速构建Hyperledger Fabric网络
    fastjson 的使用总结
    简析淘宝网的六大质量属性
    软件架构师如何工作
  • 原文地址:https://www.cnblogs.com/chentianwei/p/11871228.html
Copyright © 2011-2022 走看看