Python 3.x 处理异常并继续循环

Python 3.x 处理异常并继续循环,python-3.x,exception,python-decorators,Python 3.x,Exception,Python Decorators,我正在用Python3.7编写一个ETL框架,它将函数用作带有特殊装饰符的“任务”。这些任务中的大多数都运行一个循环。如果循环中出现异常,我希望函数通过记录有关故障的数据并继续循环来处理该异常 这是迄今为止我所拥有的一个简化示例: class TaskExecutionError(RuntimeError): def __init__(self, msg="", context={}): self.msg = msg self.context = cont

我正在用Python3.7编写一个ETL框架,它将函数用作带有特殊装饰符的“任务”。这些任务中的大多数都运行一个循环。如果循环中出现异常,我希望函数通过记录有关故障的数据并继续循环来处理该异常

这是迄今为止我所拥有的一个简化示例:

class TaskExecutionError(RuntimeError):
    def __init__(self, msg="", context={}):
        self.msg = msg
        self.context = context

    def __str__(self):
        return self.msg or "Error executing task."


def task(fn):
    @wraps(fn)
    def _wrapper(*args, **kwargs):
        start_ts = datetime.utcnow()
        try:
            return fn(*args, **kwargs)

        except TaskExecutionError as e:
            logger.exception(f"Task execution error will be logged: {e}.")
            fail_data = {
                    "task_name": fn.__name__,
                    "args": list(args),
                    "kwargs": kwargs,
                    "context": e.context,
                    "fail_message": str(e),
                    "fail_time": str(datetime.utcnow()),
                    # etc.
                }
            )
            # Write failure data in an object store

        finally:
            end_ts = datetime.utcnow()
            logger.info(f"*** Wallclock: {end_ts - start_ts}.")

    _wrapper.is_task = True
    return _wrapper


@task
def test_fail_log(a, b, c, kwa=1, kwb=2):
    """
    Test handling failures.
    """
    for i in range(10):
        if i % 3:
            raise TaskExecutionError(context={"i": i})
        else:
            print("All's well")
就我所看到的打印和保存消息而言,这工作得很好,但是,当然,一旦引发第一个异常,执行就会中断

我该如何处理这个问题,以便继续执行

似乎我不能使用非常方便的异常机制,我可能必须设计一个自定义的
handle\u failure()
函数等等。但是,当我从修饰函数中调用函数时,我不确定将函数装饰器的上下文传递给
handle failure()
函数的最佳方法

由于我将在几个
@task
修饰函数中使用此机制,因此如果可能的话,我希望有一个轻量级调用,而不需要太多参数


谢谢你的建议

我使用
inspect
解决了这个问题,我不喜欢经常使用,但在这里似乎是必要的:

def task(fn):
    @wraps(fn)
    def _wrapper(*args, **kwargs):
        start_ts = datetime.utcnow()
        try:
            return fn(*args, **kwargs)

        finally:
            end_ts = datetime.utcnow()
            logger.info(f"*** Wallclock: {end_ts - start_ts}.")

    _wrapper.is_task = True


def handle_task_failure(exc, local_ctx={}):
    caller_frame = inspect.currentframe().f_back
    wrapper_frame = caller_frame.f_back
    caller_ctx = wrapper_frame.f_locals
    print(f"Context: {caller_ctx}")
    logger.exception(f"Task execution error will be logged: {exc}.")
    fail_data = {
            "start_time": caller_ctx.get("start_ts"),
            "runid": caller_ctx.get("runid"),
            "task_name": caller_ctx.get("fn").__name__,
            "args": list(caller_ctx.get("args")),
            "kwargs": caller_ctx.get("kwargs"),
            "context": local_ctx,
            "fail_message": str(exc),
            "fail_time": str(datetime.utcnow()),
            "traceback": format_stack(caller_frame),
    }
    # Save failure data in object store

@task
def test_fail_log(a, b, c, kwa=1, kwb=2):
    """
    Test handling failures.
    """
    for i in range(10):
        try:
            if i % 3:
                raise RuntimeError("All's borked.")
            else:
                print("All's well.")
        except Exception as exc:
            handle_task_failure(exc, {"i": i})


另一方面,我不需要自定义异常类,处理故障的调用(在几次有趣的活动中重复)非常轻。

我使用
检查解决了这个问题,我不喜欢经常使用它,但在这里似乎是必要的:

def task(fn):
    @wraps(fn)
    def _wrapper(*args, **kwargs):
        start_ts = datetime.utcnow()
        try:
            return fn(*args, **kwargs)

        finally:
            end_ts = datetime.utcnow()
            logger.info(f"*** Wallclock: {end_ts - start_ts}.")

    _wrapper.is_task = True


def handle_task_failure(exc, local_ctx={}):
    caller_frame = inspect.currentframe().f_back
    wrapper_frame = caller_frame.f_back
    caller_ctx = wrapper_frame.f_locals
    print(f"Context: {caller_ctx}")
    logger.exception(f"Task execution error will be logged: {exc}.")
    fail_data = {
            "start_time": caller_ctx.get("start_ts"),
            "runid": caller_ctx.get("runid"),
            "task_name": caller_ctx.get("fn").__name__,
            "args": list(caller_ctx.get("args")),
            "kwargs": caller_ctx.get("kwargs"),
            "context": local_ctx,
            "fail_message": str(exc),
            "fail_time": str(datetime.utcnow()),
            "traceback": format_stack(caller_frame),
    }
    # Save failure data in object store

@task
def test_fail_log(a, b, c, kwa=1, kwb=2):
    """
    Test handling failures.
    """
    for i in range(10):
        try:
            if i % 3:
                raise RuntimeError("All's borked.")
            else:
                print("All's well.")
        except Exception as exc:
            handle_task_failure(exc, {"i": i})

另一方面,我不需要自定义异常类,并且处理失败的调用非常轻量级,在几个有趣的例子中重复了这个调用