我发现自己经常遇到此类问题。我有一个类似的功能
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
有这样的事情存在吗?我在谷歌上找不到它。
函数的“外部”在运行时无法访问函数内部局部变量的状态。所以这不能用装饰器来解决。
无论如何,我认为捕获错误和保存有价值的中间结果的责任应该由程序员“明确”地完成。如果您“忘记”这样做,那么它对您来说一定没那么重要。 话虽这么说,像
“在 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
中)。公平地说,我可能应该对我最初的陈述进行一些限定。当然,您非本地状态,尽管通常出于充分的理由不鼓励这样做。用超级简单的术语来说,如果在你的例子中 result
是一个全局变量(并且你在函数中声明了 global result
),那么这个实际上可以通过装饰器来解决。但我不会推荐这种方法,因为全局状态是一种反模式。 (它仍然需要您记住每次都使用您为该作业指定的任何全局变量。)