The result of tag: (3 results)

自动化Python异常显示 - better-exceptions

by LauCyun Sep 12,2017 22:09:55 25,756 views

Better Exceptions是最近一期的Python Weekly和Pycoders Weekly上都推荐的一个库,用处是展示更友好的异常信息。

无论在什么语言里遇到异常是很正常的,遇到线上异常也是常有的。本地异常的话通过pdb调试print输出关键信息是可行的,但是对于线上异常的话,只能从日志里查看,但日志里的信息可能只是提示你:KeyError: 'a',遇到这种问题,一般的做法是本地启动项目,尝试重现,这样你才能知道上下文是什么。但,往往很难复现,因为从日志里你看不到用户的输入是什么?如果你没有手动捕获异常,并把造成异常的数据写入log。

使用方法:

  • 通过pip安装better_exceptions
    $ pip install better_exceptions
  • 将它导入某处:
    import better_exceptions

栗子1:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# import better_exceptions

def division(a, b):
    return a/b

print(division(1, 0))

运行结果如下:

$ python test.py
Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(division(1, 0))
  File "test.py", line 8, in division
    return a/b
ZeroDivisionError: integer division or modulo by zero

加上better_exceptions后,其运行结果如下:

$ python test.py
Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(division(1, 0))
          └ <function division at 0x00000000036566D8>
  File "test.py", line 8, in division
    return a/b
           │ └ 0
           └ 1
ZeroDivisionError: integer division or modulo by zero

从上面的应用场景描述,以及最终呈现的结果来看,似乎是有用的。那么better_exceptions是怎么做到的呢?

看一下better_exceptions的源码:

# better_exceptions/__init__.py

...

def write_stream(data):
    if SHOULD_ENCODE:
        data = _byte(data)

        if PY3:
            STREAM.buffer.write(data)
        else:
            STREAM.write(data)
    else:
        STREAM.write(data)


def format_exception(exc, value, tb):
    formatted, colored_source = format_traceback(tb)

    if not str(value) and exc is AssertionError:
        value.args = (colored_source,)
    title = traceback.format_exception_only(exc, value)

    full_trace = u'Traceback (most recent call last):\n{}{}\n'.format(formatted, title[0].strip())

    return full_trace


def excepthook(exc, value, tb):
    formatted = format_exception(exc, value, tb)
    write_stream(formatted)


sys.excepthook = excepthook

...

主要是用了sys模块的excepthook方法,这个方法的描述如下:

ref: https://docs.python.org/2/library/sys.html#sys.excepthook

This function prints out a given traceback and exception to sys.stderr.

When an exception is raised and uncaught, the interpreter calls sys.excepthook with three arguments, the exception class, exception instance, and a traceback object. In an interactive session this happens just before control is returned to the prompt; in a Python program this happens just before the program exits. The handling of such top-level exceptions can be customized by assigning another three-argument function to sys.excepthook.

大概意思就是,如果系统抛出一个未捕获的异常,那么解释器就会调用sys.excepthook方法,同时传递三个参数:异常类(ValueError或者KeyError之类的)、异常实例traceback对象

这意味着,你可以通过重写这个方法来处理系统未捕获的异常处理。但是对于DjangoTornado这样的Web框架,没啥用。为什么呢?

栗子2:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from django.http import HttpResponse
import better_exceptions


def test(request):
    print(request.GET['q'])
    if 'q' in request.GET:
        message = u'你搜索的内容为: ' + request.GET['q']
    else:
        message = u'你提交了空表单'
    return HttpResponse(message)

这是一个view方法,运行python manage.py runserver 0.0.0.0:8000后,我在浏览器中输入http://127.0.0.1:8000/test?a=111时,并没显示像栗子1中那样的exception提示。

这是因为框架中(DjangoTornado等)会自己处理异常,所以这种hook的方式不会被触发。

...

Tags Read More


Python编程中一些异常处理的小技巧

by LauCyun Mar 15,2017 11:00:57 5,536 views

编程中经常会需要使用到异常处理的情况,在阅读了一些资料后,整理了关于异常处理的一些小技巧记录如下。

1 如何自定义异常

1.1 定义异常类

在实际编程中,有时会发现Python提供的内建异常的不够用,我们需要在特殊业务场景下的异常。这时就需要我们来定义自己的异常。按照Python约定俗成的习惯,用户定义的异常一般都是继承于Exception类,由它开始拓展。后面我们可以看到这样做在捕获异常的时候会带来很大的便利。

>>> class MyError(Exception):
        pass

>>> raise MyError(u"something error")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
__main__.MyError: something error  

1.2 API异常相关的技巧

API的异常分为定义异常与调用API时如何捕获异常两个部分,这二者相辅相成。

1.2.1 定义API异常的技巧

在自己编写API的时候,应该定义Root Exception——API中的根异常,其它异常都继承于它。这样的做法有两个好处:

  1. API代码层次更清晰

  2. API与调用程序代码隔离

假设存在如下场景:需要做一个链接数据库服务的模块。提供一个connect函数用于链接。那么,在链接的过程中,就会发生以下几种情况:

  • socket连接超时

  • socket拒绝连接

针对以上的情况,我们在模块中定义几个异常:

# database.py
class Error(Exception):
    """Root exception for all exceptions raised by this module."""
    
class SocketTimeError(Error):
    pass

class SocketRefuseError(Error):
    pass
    
def connect():
    pass    

1.2.2 调用API时异常捕获的技巧

这样在调用API的时候就可以这样使用:

try:
    connect()
except SocketTimeError as err:
    log.error(err)
except SocketRefuseError as err:
    log.error(err)
except Error as err:
    log.error("API Unexpected error:%s" % err)
except Exception:
    log.error("API bug cause exception.")    

这样精确定义多个异常,使得代码层次清晰,增强了可读性。值得注意的是:在代码的最后还捕获了Error以及Exception两个异常,这两个操作分别对应于可拓展性与健壮性的目的。

捕获Root Exception以提高可拓展性:

我们知道,在实际链接数据库时,还可能会出现用户没有登陆权限等问题。所以,我们需要在下一个版本中加入PermissionDeny这个异常。但是,旧的调用代码已经写好了,如果忘记修改的话,这个异常可能就会无法被处理,进而使得调用的程序奔溃。处于这样的考虑,我们在调用API的时候,就应该再捕获API的Root Exception,即使之后新加入了其它的异常,在这一个except中也能被捕获而不影响调用程序。使得API模块的可拓展性得到了提高。

捕获Exception以提高健壮性:

在调用API的时候,难免可能出现API内部存在bug的情况。这个时候如果捕获了Exception的话,就算API内部因为bug发生了异常,也不会影响到调用程序的正常运行。

从这两点中可以看出,要达到这种效果,其实都要依赖于常规异常继承于Exception类这个规矩。这样的架构划分所带来的好处是显而易见的。

2 与异常相关的编程艺术

2.1 异常代替返回状态码

我们经常需要编写一些工具类的函数,往往在这些函数的处理流程中,会产生很多的状态;而这些状态也是调用者需要得到的信息。很多时候,会用一些具有意义的返回值来表示函数处理的状态。

比如:

def write(content):
    if isinstance(content, basestring):
        f_handler = open("file.txt", 'w')
        try:
            f_handler.write(context)
            except Exception:
                return -2    # write file fail
        else:
            return 0    # write file succcess
        finally:
            f_hanlder.close()
    else:
        return -1    # arg type error

调用代码:

result = write()
if result == -1:
    log.error(u"type error")
elif result = -2:
    log.error(u"write error")
else:
    log.info("ok")    

这种状态码的方式使用起来特别的不方便,调用者还需要去理解每个状态码的意义,带来其它的学习成本;而且用if-else结构也不易于后期的程序拓展。所以,我们可以使用触发异常来代替返回状态码,每个异常名其实就包含了状态的意义在内(命名的艺术),使用起来也更好理解。

使用异常的方式:

class Error(Exception):
    pass
    
class OpenFileError(Error):
    pass
    
class WriteContentError(Error):
    pass    

def write(content):
    if isinstance(content, basestring):
        f_handler = open("file.txt", 'w')
        try:
            f_handler.write(context)
            except Exception:
                raise WriteContentError
        finally:
            f_hanlder.close()
    else:
        raise OpenFileError

调用代码:

try:
    write()
except OpenFileError as e:
    log.error(e)
except WriteContentError as e:
    log.error(e)
except Error:
    log.error("API Error")
except Exception
    log.error("API Bug")    
else:
    log.info("ok")

结合上面一点提到的使用API时的异常捕获,使得调用代码变得更佳灵活。

2.3 异常处理与流程控制

错误处理很重要,但如果它搞乱了代码逻辑,就是错误的做法

将异常处理与正常流程控制混为一谈时,代码是十分丑陋的。我们应该将二者分离,最好的做法就是将异常代码块抽离到另外的函数中。

try:
    action_a()
    action_b()
    action_c()
except ActionException as e:
    log.error(e)
else:
    action_d()    

将异常处理分离:

def action_executor():
    action_a()
    action_b()
    action_c()
    
def action():
    try:
        action_executor()
    except ActionException as e:
        log.error(e)
        
action()
action_d()

3 参考资料

...

Tags Read More


Python中如何在一行里获取多个异常

by LauCyun Oct 17,2016 16:42:54 7,705 views

我知道这样:

try:
    # 可能错的地方
except:
    # 如果错了执行这里

也知道这样:

try:
    # 可能错的地方
except IDontLikeYourFaceException:
    # 给爷笑一个
except YouAreTooShortException:
    # 踩高跷

但是我想在两个不同的异常里做同样的事,我能想到的办法:

try:
    # 可能错的地方
except IDontLIkeYouException:
    # 滚
except YouAreBeingMeanException:
    # 滚

有什么方法能像下面那样:

try:
    # 可能错的地方
except IDontLIkeYouException, YouAreBeingMeanException:
    # 滚

现在下面的代码根本不好使:

try:
    # 可能错的地方
except Exception, e:
    # 滚

所以有什么方法可以完成我的要求吗?

问题源于:Catch multiple exceptions in one line (except block) - Stack Overflow

用括号扩起来:

except (IDontLIkeYouException, YouAreBeingMeanException) as e:
    pass

用逗号分割的方法只能在Python2.6和2.7里好使,在Python3中则无效;现在,你应当使用as

...

Tags Read More