保存函数中的所有中间变量,以防函数失败

问题描述 投票:0回答:1

我发现自己经常遇到此类问题。我有一个类似的功能

def compute(input):
    result = two_hour_computation(input)
    result = post_processing(result)
    return result

post_processing(result)
失败。现在显而易见的事情是将函数更改为

import pickle

def compute(input):
    result = two_hour_computation(input)
    pickle.dump(result, open('intermediate_result.pickle', 'wb'))
    result = post_processing(result)
    return result

但我通常不记得以这种方式编写所有函数。我希望我有一个像这样的装饰器:

@return_intermediate_results_if_something_goes_wrong
def compute(input):
    result = two_hour_computation(input)
    result = post_processing(result)
    return result

有这样的事情存在吗?我在谷歌上找不到它。

python python-decorators scientific-computing
1个回答
1
投票

函数的“外部”在运行时无法访问函数内部局部变量的状态。所以这不能用装饰器来解决。

无论如何,我认为捕获错误和保存有价值的中间结果的责任应该由程序员“明确”地完成。如果您“忘记”这样做,那么它对您来说一定没那么重要。 话虽这么说,像

“在 A、B 或 C 引发异常的情况下执行 X”

的情况是 上下文管理器 的典型用例。您可以编写自己的上下文管理器,充当中间结果的存储桶(代替变量),并在异常退出时执行一些 save 操作。

类似这样的:

from __future__ import annotations from types import TracebackType from typing import Generic, Optional, TypeVar T = TypeVar("T") class Saver(Generic[T]): def __init__(self, initial_value: Optional[T] = None) -> None: self._value = initial_value def __enter__(self) -> Saver[T]: return self def __exit__( self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: if exc_type is not None: self.save() def save(self) -> None: print(f"saved {self.value}!") @property def value(self) -> T: if self._value is None: raise RuntimeError return self._value @value.setter def value(self, value: T) -> None: self._value = value

显然,您可以这样做,而不是在 
print(f"saved {self.value}!")

中使用

save
        with open('intermediate_result.pickle', 'wb') as f:
            pickle.dump(self.value, f)

现在您需要记住的就是将这些操作包装在 
with

语句中,并将中间结果分配给上下文管理器的

value
属性。演示:
def x_times_2(x: float) -> float:
    return x * 2


def one_over_x_minus_2(x: float) -> float:
    return 1 / (x - 2)


def main() -> None:
    with Saver(1.) as s:
        s.value = x_times_2(s.value)
        s.value = one_over_x_minus_2(s.value)
    print(s.value)


if __name__ == "__main__":
    main()

输出:

saved 2.0! Traceback (most recent call last): [...] return 1 / (x - 2) ~~^~~~~~~~~ ZeroDivisionError: float division by zero

如您所见,中间计算值
2.0

已“保存”,即使下一个函数引发了异常。

值得注意的是,在此示例中,上下文管理器仅在遇到异常时才调用 

save

,而不是在上下文“和平”退出时调用。如果你愿意,当然可以无条件地这样做。
这可能不像将装饰器放在函数上那么方便,但它可以完成工作。在我看来,你必须有意识地在这种情况下采取重要行动,这是一件好事,因为它教会你特别注意这些事情。

这是在 Python 中实现数据库事务之类的典型方法(例如在

SQLAlchemy

中)。

PS

公平地说,我可能应该对我最初的陈述进行一些限定。当然,您

可以

在函数中使用

非本地状态,尽管通常出于充分的理由不鼓励这样做。用超级简单的术语来说,如果在你的例子中 result 是一个全局变量(并且你在函数中声明了 global result

),那么这个 
 实际上可以通过装饰器来解决。但我不会推荐这种方法,因为全局状态是一种反模式。 (它仍然需要您记住每次都使用您为该作业指定的任何全局变量。)
    

© www.soinside.com 2019 - 2024. All rights reserved.