登录框架

gue*_*tli 6 python logging

想象一下,有一个框架提供了一个方法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()只执行一次?

shx*_*hx2 2

有几种方法可以实现该目标,每种方法都有其优点和缺点。

(其中一些与其他答案重叠。我无意抄袭,只是为了提供全面的答案)。


方法一:函数应该做到这一点

保证函数只执行一次的一种方法是使函数本身有状态,使其“记住”它已经被调用过。这或多或少是@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__': ....