8

Let's say I have a main program (test.py) and a little utilities program (test_utils.py) that has helper functions called by the main program. I would like to turn on debug statements in the code by passing a debug_flag boolean, which is read in via argparse.

Now I would like functions within my test_utils.py program to print debug statements as well, according to the value of debug_flag. I could always add debug_flag as a parameter to each function definition in test_utils.py and pass the parameter when the function is called, but is there a better way here, like make debug_flag a global variable? But if I do declare debug_flag to be global from test.py, how would that be imported into test_utils.py?

What would be the most elegant/Pythonic approach here?

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

EDIT1: To clarify - if I pass in the debug flag through argparse and thereby do set debug_flag to True - how would this be propagated to functions within test_utils.py?

EDIT2: Based on the suggestion by @joran-beasley, here's what I've got.

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

When I run test.py, I get:

$ python test.py -d True
No handlers could be found for logger "my_logger"
18
smci
  • 32,567
  • 20
  • 113
  • 146
Craig
  • 1,929
  • 5
  • 30
  • 51
  • 4
    You might want to use `logging` : https://docs.python.org/3.6/library/logging.html – Adonis Apr 11 '18 at 17:55
  • @Adonis lol your comment beat my answer by 56 seconds :P – Joran Beasley Apr 11 '18 at 17:56
  • `args[debug]` is an error. And `args.debug` already contains a boolean. – Daniel Apr 11 '18 at 17:59
  • Adding `global debug_flag` to `main` would make it a module-level variable instead of a function-local variable. – chepner Apr 11 '18 at 17:59
  • I guess if you want to use that variable for more than logging, a Java kind of approach would be to store this variable inside a "config" class (which would store other configurations as well) @JoranBeasley Great minds think alike! – Adonis Apr 11 '18 at 18:09
  • You can achieve a "true" multi-module global (or at least something that behaves like one) by passing an instance of a user-defined class from module to module via the import statement. This is what many projects with complex configurations (eg ['rcParams' in `matplotlib`](https://matplotlib.org/users/customizing.html#dynamic-rc-settings)) do. In fact, any mutable will do, so you could also just pass around a `list` or a `dict`. – tel Apr 11 '18 at 18:24

3 Answers3

6

use logging

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

then use

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

you can then do things where you have multiple levels of logging

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

if for some reason you needed to retrieve the currentEffectiveLogLevel (you really shouldnt in most cases ... simply use log.debug when you want debug level output)

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

[edit to clarify]

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))

or you can wrie a helper funtion

def is_debug():
    return logging.getLogger("my_logger").getEffectiveLevel() == logging.DEBUG

of coarse you can always do some horrible hacky crap also like writing it to a flat file and reading it in or using truly global variables (harder than you might think and lots of edge cases to worry about)

Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
  • But the `args.debug` parm is read in from `test.py` which imports `test_utils.py` - I'm looking for a way for functions within `test_utils.py` to be able to use `args.debug` – Craig Apr 11 '18 at 18:02
  • you set the log level based on that ... you then output to the appropriate log channel automagically... you can also get the logs level from anywhere and use that to determine if the flag was passed in ... – Joran Beasley Apr 11 '18 at 18:15
  • But this appears to work only if I set the logging in program A and call summation from within that program. Could you check my EDIT2? I've added what I'm running now – Craig Apr 11 '18 at 18:29
  • @Craig just add `logging.basicConfig()` at the top of main.py ... see https://repl.it/repls/TestyGruesomeNumbers – Joran Beasley Apr 11 '18 at 20:16
1

You can acheive a "package-wide" global in Python by passing around a mutable. My favored approach in these situations is to create a custom Flags class. You then share an instance of Flags between all of your modules, and its attributes function like globals. Here's an example in terms of the code you posted:

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()

I moved flags to test_utils.py to avoid a circular import issue, but that shouldn't affect anything. A more robust solution (appropriate for larger projects) would be to have a separate config.py (or something) module in which flags is initialized.

tel
  • 13,005
  • 2
  • 44
  • 62
0

Simple answer. Assign a dict in the main file. Then import it via all the other modules.

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()

Now anytime you import the debug_flag from the main module, it will have whatever setting you set the flag too once you processed the arg parse. You could then change this at anytime, and subsequent calls will pick up the change.

eatmeimadanish
  • 3,809
  • 1
  • 14
  • 20
  • 1
    You have a syntax error (should be `from test import debug_flag`) and a more serious semantic error. Since the OP is also importing `summation` from test_utils.py in test.py, the way you currently have the imports organized will raise an `ImportError` due to the circular imports. – tel Apr 11 '18 at 20:07
  • I was just showing a use case, the way he has his imports is irrelevant, his code was unusable to begin with. args[debug] makes no sense. His code is going to have to be rewritten regardless. – eatmeimadanish Apr 11 '18 at 20:32