我想达到这样的效果:

def foo():
   try:
       raise IOError('Stuff ')
   except:
       raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
       e.message = e.message + 'happens at %s' % arg1
       raise

bar('arg1')
Traceback...
  IOError('Stuff Happens at arg1')

但我得到的是:

Traceback..
  IOError('Stuff')

关于如何实现这一点,有什么线索吗?如何在Python 2和3中都做到这一点?


当前回答

我会这样做,这样在foo()中改变它的类型就不需要在bar()中也改变它。

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
        foo()
    except Exception as e:
        raise type(e)(e.message + ' happens at %s' % arg1)

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1

更新1

这里有一个轻微的修改,保留了原始的回溯:

...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e), type(e)(e.message +
                               ' happens at %s' % arg1), sys.exc_info()[2]

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    foo()
  File "test.py", line 5, in foo
    raise IOError('Stuff')
IOError: Stuff happens at arg1

更新2

对于Python 3。在2012-05-16对PEP 352的修改中(我的第一次更新发布于2012-03-12),我的第一次更新中的代码在语法上是错误的,加上在BaseException上有消息属性的想法被撤销了。因此,目前在Python 3.5.2中,您需要按照这些行做一些事情来保留回溯,而不是在函数栏()中硬编码异常的类型。还要注意,会有这样一行:

During handling of the above exception, another exception occurred:

在显示的回溯消息中。

# for Python 3.x
...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e)(str(e) +
                      ' happens at %s' % arg1).with_traceback(sys.exc_info()[2])

bar('arg1')

更新3

一位评论者询问是否有一种方法可以同时在Python 2和3中工作。尽管由于语法差异,答案似乎是“否”,但有一种解决方法,即使用six外接组件模块中的rerraise()等帮助函数。因此,如果您出于某种原因不想使用这个库,下面是一个简化的独立版本。

还要注意,由于异常是在rerraise()函数中重新引发的,因此无论引发什么跟踪,都会出现异常,但最终结果是您想要的结果。

import sys

if sys.version_info.major < 3:  # Python 2?
    # Using exec avoids a SyntaxError in Python 3.
    exec("""def reraise(exc_type, exc_value, exc_traceback=None):
                raise exc_type, exc_value, exc_traceback""")
else:
    def reraise(exc_type, exc_value, exc_traceback=None):
        if exc_value is None:
            exc_value = exc_type()
        if exc_value.__traceback__ is not exc_traceback:
            raise exc_value.with_traceback(exc_traceback)
        raise exc_value

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
        reraise(type(e), type(e)(str(e) +
                                 ' happens at %s' % arg1), sys.exc_info()[2])

bar('arg1')

其他回答

在PEP 678中,本机支持向异常添加注释:

try:
  raise TypeError('bad type')
except Exception as e:
  e.add_note('Add some information')
  raise

呈现为:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: bad type
Add some information

我跳它可以取代史蒂夫霍华德的解决方案,不幸的是,它不给用户任何控制如何格式化最终的异常(例如,不能在异常之前添加一个注释,如:'错误在fn: {original_exc}')

如果想要对回溯进行更多控制,可以使用https://github.com/google/etils:

from etils import epy

with epy.maybe_reraise('Error in fn: '):
  fn()

Or:

try:
  fn()
except Exception as e:
  epy.reraise(e, suffix='. Did you mean y ?')

我在代码中使用:

try:
    a=1
    b=0
    c=a/b

except:
    raise Exception(f"can't divide {a} with {b}")

输出:

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_11708/1469673756.py in <module>
      3     b=0
----> 4     c=a/b
      5 

ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Exception                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_11708/1469673756.py in <module>
      5 
      6 except Exception:
----> 7     raise Exception(f"can't divide {a} with {b}")

Exception: can't divide 1 with 0

如果你来这里寻找Python 3的解决方案,手册上说:

当引发一个新的异常时(而不是使用一个简单的raise来重新引发当前正在处理的异常),隐式异常上下文可以通过使用from和raise来补充显式原因:

raise new_exc from original_exc

例子:

try:
    return [permission() for permission in self.permission_classes]
except TypeError as e:
    raise TypeError("Make sure your view's 'permission_classes' are iterable. "
                    "If you use '()' to generate a set with a single element "
                    "make sure that there is a comma behind the one (element,).") from e

最后是这样的:

2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
    return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable 

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
    # Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If 
     you use parens () to generate a set with a single element make 
     sure that there is a (comma,) behind the one element.

将一个完全无描述的TypeError转换为一个带有解决方案提示的漂亮消息,而不会搞乱原始异常。

也许

except Exception as e:
    raise IOError(e.message + 'happens at %s'%arg1)

这是我的实现,使用它作为上下文管理器,并可选地添加额外的消息异常:

from typing import Optional, Type
from types import TracebackType

class _addInfoOnException():
    def __init__(self, info: str = ""):
        self.info = info

    def __enter__(self):
        return

    def __exit__(self,
                 exc_type: Optional[Type[BaseException]],
                 exc_val: BaseException,  # Optional, but not None if exc_type is not None
                 exc_tb: TracebackType):  # Optional, but not None if exc_type is not None
        if exc_type:
            if self.info:
                newMsg = f"{self.info}\n\tLow level error: "
                if len(exc_val.args) == 0:
                    exc_val.args = (self.info, )
                elif len(exc_val.args) == 1:
                    exc_val.args = (f"{newMsg}{exc_val.args[0]}", )
                elif len(exc_val.args) > 0:
                    exc_val.args = (f"{newMsg}{exc_val.args[0]}", exc_val.args[1:])
            raise

用法:

def main():
    try:
        raise Exception("Example exception msg")
    except Exception:
        traceback.print_exc()
        print("\n\n")

    try:
        with _addInfoOnException():
            raise Exception("Example exception msg, no extra info")
    except Exception:
        traceback.print_exc()
        print("\n\n")

    try:
        with _addInfoOnException("Some extra info!"):
            raise Exception("Example exception msg")
    except Exception:
        traceback.print_exc()
        print("\n\n")


if __name__ == "__main__":
    main()

这将在这样的跟踪中解决:

Traceback (most recent call last):
  File "<...>\VSCodeDevWorkspace\testis.py", line 40, in main
    raise Exception("Example exception msg")
Exception: Example exception msg



Traceback (most recent call last):
  File "<...>\VSCodeDevWorkspace\testis.py", line 47, in main
    raise Exception("Example exception msg, no extra info")
  File "<...>\VSCodeDevWorkspace\testis.py", line 47, in main
    raise Exception("Example exception msg, no extra info")
Exception: Example exception msg, no extra info



Traceback (most recent call last):
  File "<...>\VSCodeDevWorkspace\testis.py", line 54, in main
    raise Exception("Example exception msg")
  File "<...>\VSCodeDevWorkspace\testis.py", line 54, in main
    raise Exception("Example exception msg")
Exception: Some extra info!
        Low level error: Example exception msg