Reputation: 5844
First, the scenario. I have a module config.py:
DEBUG = False # Set to True with --debug option
And another module do.py that uses it:
from config import DEBUG
def do_something():
if DEBUG:
print 'Debug...'
print 'do something'
And in the main.py:
import config
import do
import sys
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == '--debug':
config.DEBUG = True
do.do_something()
Now, when I run it:
$ python2.7 main.py --debug
do something
So in do_something()
, DEBUG
is False.
What's the best way of having a configuration flag like this that:
Upvotes: 1
Views: 147
Reputation: 59681
I guess it's worth noting that
from config import DEBUG
imports the value of DEBUG
(which is False
) before you've set it. Later when you change it, your do
module is still pointing to the old False
value. If you want to make this more dynamic, you could use a function in your config:
# config.py
is_debug = False
def debug():
return is_debug
Then in your do.py
# do.py
from config import debug
def do_something():
if debug():
print 'Debug...'
print 'do something'
Upvotes: 0
Reputation: 365945
What you have is probably the best way. In general, variables in Python belong somewhere, and DEBUG
belongs in config
more than anywhere else. If you want to import it into another module, that's fine, but you want to do it explicitly, as you're doing, not through magic.
It's worth noting that if the only thing you ever do with DEBUG
is if DEBUG: print <stuff>
you may be better off with a function:
config.debug(stuff)
And at that point, you may want to look at logging
instead of reinventing a third of a wheel and hoping you don't need the other two thirds.
However, if you really want "works, concise, safe" without regard for pythonicity or portability, there is an alternative: the __builtin__
module (renamed builtins
in Python 3.x).
In main.py:
import __builtin__
import do
import sys
if __name__ == '__main__':
__builtin__.DEBUG = len(sys.argv) > 1 and sys.argv[1] == '--debug'
do.do_something()
In do.py, no need to import anything:
def do_something():
if DEBUG:
print 'Debug...'
print 'do something'
Of course if you write another script, like test.py
, that imports do
without importing main
, this will raise a NameError
. But you'd get the same error from from config import DEBUG
or config.DEBUG
if you tried to do that before importing main
, so it's no worse.
Why does this work? From the docs:
CPython implementation detail: Most modules have the name
__builtins__
(note the 's') made available as part of their globals. The value of__builtins__
is normally either this module or the value of this modules’s__dict__
attribute. Since this is an implementation detail, it may not be used by alternate implementations of Python.
In particular, whenever you import
a module or run a top-level script, unless you've done something weird with an import hook, the exec
that executes the module's bytecode will ensure that this happens. Which means that, as long as the module doesn't shadow DEBUG
(which is always a possibility, of course) or screw with its globals, DEBUG
will always be found in the builtin dict. (If you exec
any code of your own to explicitly build a types.ModuleType
, and pass an explicit globals
instead of inheriting it, you'll have to remember to do the same thing, but hopefully that isn't an issue.)
It happens to work in at least PyPy 2.x and 3.x and Jython 2.5 and 2.7. Beyond that? Who knows; it's not guaranteed.
Upvotes: 1
Reputation: 5844
Addressing point 1, I can write do.py like this:
import config
def do_something():
if config.DEBUG:
print 'Debug...'
print 'do something'
but now config.DEBUG
isn't as concise (point 2).
Or I could write config.py like this:
class Debug(object):
_DEBUG = False
def __call__(self):
return self._DEBUG
def set(self, debug):
self._DEBUG = debug
DEBUG = Debug()
But that allows for simple mistakes like this:
>>> import config
>>> config.DEBUG()
False
>>> from config import DEBUG
>>> DEBUG()
False
>>> DEBUG.set(True)
>>> DEBUG()
True
>>> DEBUG.set(False)
>>> DEBUG()
False
>>> if DEBUG: print 'Oops'
...
Oops
which violates point 3.
So is there any better solution?
Upvotes: 1