bounty还有5天到期。此问题的答案有资格获得+50声望奖励。Alex Lenail正在寻找一个规范答案。
我发现自己经常遇到这类问题。我有一个函数,
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
这样的东西存在吗?我在google上找不到。
3条答案
按热度按时间p8h8hvxi1#
函数的“外部”在运行时无法访问函数内部的局部变量的状态。所以这个问题不能用装饰器来解决。
在任何情况下,我都认为捕捉错误和保存有价值的中间结果的责任应该由程序员明确地完成。如果你“忘记”了做这件事,那对你来说一定不那么重要。
话虽如此,像 “在A、B或C引发异常的情况下执行X” 这样的情况是上下文管理器的典型用例。您可以编写自己的上下文管理器,它充当中间结果的存储桶(代替变量),并在异常退出时执行一些
save
操作。大概是这样的:
显然,你可以这样做,而不是在
save
中使用print(f"saved {self.value}!")
:现在,您需要记住的是将这些操作 Package 在
with
-语句中,并将中间结果分配给上下文管理器的value
属性。演示:输出:
正如您所看到的,中间计算值
2.0
被“保存”了,即使下一个函数引发了异常。值得注意的是,在本例中,上下文管理器仅在遇到异常时才调用
save
,而不是在上下文“和平”退出时。如果你愿意的话,你当然可以无条件地这么做。这可能不像只是在函数上添加装饰器那么方便,但它可以完成工作。在我看来,你必须有意识地在这种情况下 Package 你的重要行动,这是一件好事,因为它教会你特别注意这些事情。
这是在Python中实现数据库事务之类的东西的典型方法(例如,在SQLAlchemy中)。
PS
为了公平起见,我可能应该对我最初的陈述做一点修改。当然,你可以在函数中使用non-localstate,尽管这通常是不被鼓励的。用超级简单的术语来说,如果在你的例子中
result
是一个全局变量(你在函数中声明了global result
),这实际上可以通过装饰器来解决。但我不推荐这种方法,因为全局状态是一种反模式。(它仍然需要你记住每次使用你为该作业指定的任何全局变量。tag5nh1u2#
我并不是说这是一个好主意,但在函数引发异常后,读取函数的局部变量是可能的:
你当然可以把它 Package 在一个装饰器里
这个实现只打印局部变量(
Mapping[str, Any]
)。用法是这样的:
assert
成功,第二个调用打印局部变量({'crash': True, 'expensive': 6}
),然后再次引发RuntimeError。但还有一个选择。如果你记得在你的函数中加入一个装饰器,你还可以添加一个最小干扰的安全特性。这个想法是
yield
每个结果,这是重要的,然后return
最终结果。所以你的函数看起来像这样:然后装饰器可以运行函数(不是像OP建议的那样逐行运行,而是逐yield运行)并收集产生的结果:
每当生成器产生时,我们存储结果。当它引发Exception时,存储的结果被保存(
print
),当它完成时(StopIteration
),结果只是返回。注意:decorator的类型是正确的。即
reveal_type(foo(...))
是int
,当你忘记产生一个结果,mypy会抱怨这个不兼容的返回值类型(得到“int”,应为“Generator[Any,None,int]”)
不是很漂亮,但也是个好东西。
注2:我省略了
@functools.wraps
以缩短代码yftpprvb3#
这可以用装饰器来完成,但装饰器应该在底层函数上,主要是因为它要简单得多。假设您只想重用计算,那么缓存系统应该是理想的。
我实现该高速缓存的方式非常简单,装饰器
@cache.result
获取调用签名(函数名和参数的md5),无论函数返回什么,如果函数完成,结果都作为文件写入磁盘;@cache.with_key('key')
它是相同的,但全局的,任何函数,得到装饰与它和相同的关键字将返回相同的缓存值;在这两种情况下,装饰器都没有向所使用的函数添加额外的代码或复杂性。顺便说一下,没有必要使用
cache.<func>
作为装饰器,它也可以用作常规函数。