TimB
TimB

Reputation: 5844

Is there a better way to import variables from a Python module?

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:

  1. works,
  2. is concise, and
  3. is safe?

Upvotes: 1

Views: 147

Answers (3)

Martin Konecny
Martin Konecny

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

abarnert
abarnert

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

TimB
TimB

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

Related Questions