umläute
umläute

Reputation: 31354

overriding a set of namespaced constants

in order to minimize hardcoded values throughout my program, i have defined a set of constants, like the following Constants.py:

FOO = 42
HOSTNAME=socket.gethostname()
BAR = 777

all modules that want to use these constants, simply do an import Constants and use Constants.FOO right away.

now some of these constants may depend on the actual host the program is running on. therefore i would like to override some of them selectively, based on the actual environment the application is running in. a first crude attempt looked like the following Constants.py:

FOO = 42
HOSTNAME=socket.gethostname()
if HOSTNAME == 'pudel':
   BAR = 999
elif HOSTNAME == 'knork'
   BAR = 888
else:
   BAR = 777

while this works ok, it clutters the file with special cases, which i would like to avoid.

if i was in doing shell-scripting i would use something like this Constants.sh:

 FOO = 42
 HOSTNAME=$(hostname)
 BAR = 777

 # load host-specific constants
 if [ -e Constants_${HOSTNAME}.sh ]; then
   . Constants_${HOSTNAME}.sh
 fi

and an optional Constants_pudel.sh, that looks like:

BAR = 999

which keeps common constants together and allows to easily override them in separate files.

since i'm not writing a shell-script but a python program, i was wondering how to acchieve the same result.

to no avail i tried something like:

FOO = 42
HOSTNAME=socket.gethostname()
BAR = 777
try:
  __import__('Constants_'+HOSTNAME)
except ImportError:
  pass

and Constants_poodle.py would look like:

import Constants
Constants.BAR = 999

this works ok, within Constants_poodle, but when i try to import Constants in another python file, i get the original Constants.BAR.

apart from not working at all, using __import__() seems exceptionally ugly, so i guess there is a proper way to override exported constants for specific setups?

Upvotes: 0

Views: 552

Answers (2)

martineau
martineau

Reputation: 123501

You could do something like that with the following, which is derived from the Activestate Constants in Python recipe:

import os as _os
import socket as _socket

class _constants(object):
    def __init__(self):
        self.FOO = 42
        self.HOSTNAME = _socket.gethostname()
        self.BAR = 777

        # load host-specific constants
        hostconst = 'Constants_{}'.format(self.HOSTNAME)
        if _os.path.exists(hostconst):
            localdict = {}
            execdict = self.__dict__.copy()
            execdict['__builtins__'] = None
            execfile(hostconst, execdict, localdict)
            self.__dict__.update(localdict) # add/override attributes defined

    def __setattr__(self, name, value):
        self.__dict__[name] = value

# replace module entry in sys.modules[__name__] with instance of _constants
# (and create additional reference to module so it's not deleted --
# see Stack Overflow question: http://bit.ly/ff94g6)
import sys
_ref, sys.modules[__name__] = sys.modules[__name__], _constants()

if __name__ == '__main__':
    import constants

    print constants.FOO
    print constants.HOSTNAME
    print constants.BAR

So, for example if _socket.gethostname() returned 'pudel', and a Constants_pudel file existed containing these lines:

BAR = 999
FOO += 1

Then the output from the print statements would be:

43   
pudel
999  

Upvotes: 1

tdelaney
tdelaney

Reputation: 77397

Your solution has several problems. First, import doesn't add the imported module to the current namespace. Not sure about python 3.x, but on python 2.x You could do something like:

FOO = 42
BAR = 777
HOSTNAME=socket.gethostname()
try:
    _imp = __import__('Constants_'+HOSTNAME)
    _locals = locals()
    for _name in dir(_imp):
        if not _name.startswith('_'):
            _locals[_name] = getattr(_imp, _name)
    del _imp, _locals, _name
except ImportError:
    pass

But the next problem is that all of the constants_xxx.py files would have to be in the python path.

An alternate solution that I think would work better for you is to put the configuration in an .ini file in the user directory and use ConfigParser (or yaml or xml depending on your tastes).

Upvotes: 1

Related Questions