Reputation: 31354
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
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
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