想象一下,有一个框架提供了一个方法logutils.set_up(),根据一些配置设置日志记录.
应尽早设置日志记录,因为导入库期间发出的警告不应丢失.
由于旧的way(if __name__=='__main__':)看起来很丑,我们使用console_script入口点来注册该main()方法.
# foo/daily_report.py
from framework import logutils
logutils.set_up()
def main():
...
Run Code Online (Sandbox Code Playgroud)
我的问题是logutils.set_up()可能会被调用两次:
想象一下,有一个第二个控制台脚本调用logutils.set_up()和imports daily_report.py.
我可以更改框架代码并set_up()在第二次调用中什么也不做logutils.set_up(),但这感觉很笨拙.我想避免它.
我怎么能确定logutils.set_up()只执行一次?
有几种方法可以实现该目标,每种方法都有其优点和缺点。
(其中一些与其他答案重叠。我无意抄袭,只是为了提供全面的答案)。
方法一:函数应该做到这一点
保证函数只执行一次的一种方法是使函数本身有状态,使其“记住”它已经被调用过。这或多或少是@eestrada 和@qarma 所描述的。
至于实现这一点,我同意 @qarma 的观点,即使用记忆化是最简单、最理想的方法。互联网上有一些简单的 python 记忆装饰器。标准库中包含的一个是functools.lru_cache. 您可以简单地使用它,例如:
@functools.lru_cache
def set_up(): # this is your original set_up() function, now decorated
<...same as before...>
Run Code Online (Sandbox Code Playgroud)
这里的缺点是,可以说set_up维护状态不是 的责任,它只是一个函数。人们可以说它应该执行两次,并且调用者有责任仅在需要时调用它(如果您确实想运行它两次怎么办)?一般的论点是,函数(为了有用和可重用)不应该对其调用的上下文做出假设。
这个论点在你的情况下有效吗?由您决定。
这里的另一个缺点是,这可以被视为滥用记忆工具。Memoization是与函数式编程密切相关的工具,应该应用于纯函数。记住一个函数意味着“不需要再次运行它,因为我们已经知道结果”,而不是“不需要再次运行它,因为我们想避免一些副作用”。
方法二:你认为丑的(if __name__=='__main__')
您在问题中已经提到的最常见的Python方式是使用臭名昭著的if __name__=='__main__'构造。
这保证了该函数仅被调用一次,因为它仅从名为 的模块中调用__main__,并且解释器保证您的进程中只有一个这样的模块。
这有效。没有任何并发症或警告。这是在 python 中运行主代码(包括设置代码)的方式。它被认为是Pythonic只是因为它在Python中非常常见(因为没有更好的方法)。
唯一的缺点是它可以说是丑陋的(从美观角度来看,而不是从代码质量角度来看)。我承认,在我最初几次看到或写下它时,我也皱起了眉头,但你会越来越喜欢它。
方法 3:利用 python 的模块导入机制
Python 已经有一个缓存机制来防止模块被双重导入。您可以通过在新模块中运行设置代码然后导入它来利用此机制。这与@rll 的答案类似。这很简单,要做到:
# logging_setup.py
from framework import logutils
logutils.set_up()
Run Code Online (Sandbox Code Playgroud)
现在,每个调用者都可以通过导入新模块来运行它:
# foo/daily_report.py
import logging_setup # side effect!
def main():
...
Run Code Online (Sandbox Code Playgroud)
由于模块仅导入一次,set_up因此仅调用一次。
这里的缺点是它违反了“显式优于隐式”的原则。也就是说,如果你想调用一个函数,就调用它。运行对模块导入时间有副作用的代码并不是一个好习惯。
方法 4:猴子修补
这是迄今为止该答案中最糟糕的方法。不要使用它。但这仍然是完成工作的一种方法。
这个想法是,如果您不希望该函数在第一次调用后被调用,请在第一次调用后对其进行猴子修补(阅读:破坏它)。
from framework import logutils
logutils.set_up_only_once()
Run Code Online (Sandbox Code Playgroud)
可以在哪里set_up_only_once实现,例如:
def set_up_only_once():
# run the actual setup (or nothing if already vandalized):
set_up()
# vandalize it so it never walks again:
import sys
sys.modules['logutils'].set_up = lambda: None
Run Code Online (Sandbox Code Playgroud)
缺点:你的同事会讨厌你。
长话短说:
最简单的方法是使用记忆functools.lru_cache,但这可能不是代码质量方面的最佳解决方案。该解决方案是否足够适合您的情况取决于您。
最安全、最 Pythonic 的方法,虽然不赏心悦目,但使用if __name__=='__main__': ....
| 归档时间: |
|
| 查看次数: |
335 次 |
| 最近记录: |