Python 2和3兼容从stacktrace中隐藏装饰器的方法

时间:2014-05-20 16:53:03

标签: python python-2.7 python-3.x decorator stack-trace

我写了一个我不想在堆栈跟踪中显示的装饰器。所以在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中做到这一点?

1 个答案:

答案 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会在您重新启动时将其重新标记 - 例外!