如何在我的代码中传递全局调试标志变量;我应该使用 argparse 吗?

8
假设我有一个主程序(test.py)和一个小工具程序(test_utils.py),其中包含由主程序调用的辅助函数。 我想通过传递一个名为“debug_flag”的布尔值来在代码中打开调试语句,该布尔值是通过argparse读取的。
现在,我希望test_utils.py程序内部的函数也能根据debug_flag的值打印调试语句。我可以在每个函数定义中添加debug_flag作为参数,并在调用函数时传递参数,但是否有更好的方法,在此处将debug_flag设置为全局变量?但是如果我从test.py声明debug_flag为全局变量,那么它会被导入到test_utils.py中吗?
如何在这里使用最优雅/Pythonic的方法?
test.py:
import argparse
from test_utils import summation

def main():
    args = get_args()
    debug_flag = True if args[debug] == 'True' else False
    print summation(5, 6, 7)

def get_args():
    parser = argparse.ArgumentParser(description='Test program')
    parser.add_argument('-d','--debug', help='Debug True/False', default=False)
    args = vars(parser.parse_args())
    return args

test_utils.py:

from test import debug_flag

def summation(x, y, z):
    if debug_flag:
        print 'I am going to add %s %s and %s' % (x, y, z)
    return x + y + z

编辑1:为了澄清 - 如果我通过argparse传递调试标志,并因此将debug_flag设置为True - 如何将其传播到test_utils.py中的函数?

编辑2:根据@joran-beasley的建议,这是我得到的。

test.py:

import argparse
import logging
from test_utils import summation

def main():
    args = get_args()
    logging.getLogger("my_logger").setLevel(logging.DEBUG if args['debug'] == 'True' else logging.WARNING)
    print summation(5, 6, 7)

def get_args():
    parser = argparse.ArgumentParser(description='Test program')
    parser.add_argument('-d','--debug', help='Debug True/False', required=True)
    args = vars(parser.parse_args())
    return args

main()

test_utils.py

import logging

log = logging.getLogger('my_logger')

def summation(x, y, z):
    log.debug('I am going to add %s %s and %s' % (x, y, z))
    return x + y + z

当我运行test.py时,我得到了以下结果:
$ python test.py -d True
No handlers could be found for logger "my_logger"
18

4
你可能想要使用 logging :https://docs.python.org/3.6/library/logging.html - Adonis
@Adonis 哈哈,你的评论比我的回答快了56秒 :P - Joran Beasley
args[debug] 是一个错误。而且 args.debug 已经包含了一个布尔值。 - Daniel
global debug_flag 添加到 main 中会使其成为模块级变量,而不是函数局部变量。 - chepner
我猜如果你想在记录之外使用那个变量,Java风格的方法是将此变量存储在“config”类内(该类还会存储其他配置信息)@JoranBeasley 伟大的思想总是相似的! - Adonis
通过使用导入语句将用户定义类的实例从模块传递到模块,您可以实现“真正”的多模块全局(或至少是行为类似的)。这就是许多具有复杂配置(例如matplotlib中的'rcParams')的项目所做的。事实上,任何可变对象都可以,因此您也可以传递一个listdict - tel
3个回答

6

使用logging模块

# this logger will always now be logging.DEBUG level
logging.getLogger("my_logger").setLevel(logging.DEBUG if args.debug else logging.WARNING)

然后使用。
log = logging.getLogger("my_logger")
...
log.warn("some text that should always be seen!")
log.debug("some debug info!")

你可以使用多级日志记录来进行一些操作

log_level = logging.WARNING
if args.verbose > 0:
   log_level = logging.INFO
elif args.verbose > 3:
   log_level = logging.DEBUG

如果出于某些原因需要检索currentEffectiveLogLevel(在大多数情况下,您真的不应该这样做...只需在需要调试级别输出时使用log.debug即可)

logging.getLogger("my_logger").getEffectiveLevel()

[编辑以澄清]

log = logging.getLogger('my_logger')

def summation(x, y, z):
   log.debug('I am going to add %s %s and %s' % (x, y, z)) # will not print if my_logger does not have an effective level of debug
   return x + y + z

print(summation(2, 3, 4))
log.setLevel(logging.DEBUG)
print(summation(4, 5, 6))

或者您可以编写一个辅助函数。
def is_debug():
    return logging.getLogger("my_logger").getEffectiveLevel() == logging.DEBUG

当然,你总是可以使用一些可怕的hacky技巧,比如将其写入平面文件并读取它或使用真正的全局变量(比你想象的更难,并且需要担心很多边缘情况)。

但是args.debug参数是从test.py中读取的,而test.py导入了test_utils.py - 我正在寻找一种让test_utils.py内部的函数能够使用args.debug的方法。 - Craig
你可以根据日志级别设置日志记录级别,然后自动输出到相应的日志通道...你还可以从任何地方获取日志级别,并使用它来确定是否传递了标志... - Joran Beasley
但是这似乎只在我设置程序A中的日志记录并从该程序内调用求和时才起作用。您能检查我的EDIT2吗?我已经添加了我现在正在运行的内容。 - Craig
@Craig 只需在 main.py 的顶部添加 logging.basicConfig() ... 请参见 https://repl.it/repls/TestyGruesomeNumbers - Joran Beasley

1
你可以通过传递一个可变对象来实现Python中的“包范围”全局变量。在这种情况下,我喜欢采用创建自定义Flags类的方法。然后,在所有模块之间共享Flags的实例,其属性就像全局变量一样。以下是以你发布的代码为例的示例:

test_utils.py

__all__ = ['flags', 'summation']

class Flags(object):
    def __init__(self, *items):
        for key,val in zip(items[:-1], items[1:]):
            setattr(self,key,val)

flags = Flags('debug', False)

def summation(x, y, z):
    if flags.debug:
        print 'I am going to add %s %s and %s' % (x, y, z)
    return x + y + z

test.py

#!/usr/bin/env python2
import argparse
import sys

from test_utils import summation, flags

def main():
    args = get_args()
    flags.debug = args['debug']
    print summation(5, 6, 7)

def get_args():
    parser = argparse.ArgumentParser(description='Test program')
    parser.add_argument('-d','--debug', action='store_true', help='Debug True/False', default=False)
    args = vars(parser.parse_args())
    return args

if __name__=='__main__':
    main()

我将标志移到test_utils.py中,以避免循环导入问题,但这不应该影响任何内容。对于更大的项目,更健壮的解决方案是创建单独的config.py(或其他)模块,在其中初始化flags


但问题在于我希望test_utils中的函数使用debug_flag - Craig

0
简单的回答。在主文件中分配一个字典。然后通过所有其他模块导入它。

Test.py

debug_flag = {'setting' : False}

test_utils.py

from test import debug_flag

def print_flag():
    print(debug_flag['setting'])

def set_flag(setting):
    debug_flag['setting'] = setting

set_flag(True)
print_flag()

现在,每当您从主模块导入debug_flag时,它将具有您在处理arg parse时设置的标志设置。然后,您可以随时更改此设置,并且后续调用将会捕捉到更改。


1
你有一个语法错误(应该是 from test import debug_flag),还有一个更严重的语义错误。由于 OP 在 test.py 中也从 test_utils.py 导入了 summation,所以你目前组织导入的方式会因为循环导入而引发 ImportError - tel
我只是展示了一个使用案例,他的导入方式并不重要,他的代码一开始就无法使用。args[debug] 没有意义。无论如何,他的代码都需要被重写。 - eatmeimadanish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接