我写了一个我不想在堆栈跟踪中显示的装饰器。所以在Python2中我会这样做:
class SneakyDecorator(object):
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
# [...]
try:
self.f(*args, **kwargs)
except:
t, v, tb = sys.exc_info()
raise v, None, tb.tb_next # <=== Important line
在Python3中,我这样做:
class SneakyDecorator:
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
# [...]
try:
self.f(*args, **kwargs)
except:
t, v, tb = sys.exc_info()
raise v.with_traceback(tb.tb_next) # <=== Important line
所以问题是:有没有办法以兼容Python2和Python3的方式做到这一点?我更喜欢不涉及两个独立代码的解决方案 - 基地。
我尝试使用reraise
模块的six
函数,但问题是,此函数会出现在堆栈跟踪中。
如果您按照以下答案执行此操作相同的问题:Exception with original traceback - 2.6-3.X compatible version。
更新:Python3代码不起作用!调用方法仍显示在堆栈跟踪中。所以后续问题是:有没有办法在Python3中做到这一点?
答案 0 :(得分:3)
使这项工作的唯一方法是保持__call__
方法的单独版本,因为 exec 本身会创建一个堆栈帧!以下内容在Python 2中再次展示了框架:
class SneakyDecorator:
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
# [...]
try:
self.f(*args, **kwargs)
except:
t, v, tb = sys.exc_info()
exec("raise v, None, tb.tb_next")
结果:
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 12, in __call__
File "<stdin>", line 3, in f
ValueError: Oi!
所以使用:
def __call__(self, *args, **kwargs):
# [...]
try:
self.f(*args, **kwargs)
except:
t, v, tb = sys.exc_info()
if sys.version_info[0] <= 2:
exec("raise v, None, tb.tb_next")
else:
raise v.with_traceback(tb.tb_next)
实际上没有工作。
对于Python 2,以下内容与两个单独的类具有完全相同的功能:
if sys.version_info[0] <= 2:
_call = '''\
def _call(self, *args, **kwargs):
# [...]
try:
self.f(*args, **kwargs)
except:
t, v, tb = sys.exc_info()
raise v, None, tb.tb_next
'''
exec(_call)
else:
def _call(self, *args, **kwargs):
# [...]
try:
self.f(*args, **kwargs)
except:
t, v, tb = sys.exc_info()
raise v.with_traceback(tb.tb_next)
class SneakyDecorator:
def __init__(self, f):
self.f = f
__call__ = _call
# niceties, patch up name and qualified name. Optional.
__call__.__name__ = '__call__'
__call__.__qualname__ = 'SneakyDecorator.__call__'
但是,请注意,对于Python 3,Exception.with_traceback()
方法可能允许您附加删除当前帧的新回溯,但Python会在您重新启动时将其重新标记 - 例外!