覆盖一组命名空间常量
为了减少程序中硬编码的值,我定义了一组常量,像下面这个 Constants.py
文件:
FOO = 42
HOSTNAME=socket.gethostname()
BAR = 777
所有想使用这些常量的模块,只需执行 import Constants
,然后就可以直接使用 Constants.FOO
。
不过,有些常量可能会依赖于程序运行的具体主机。因此,我想根据应用程序运行的实际环境,有选择地覆盖其中的一些常量。
我最初的尝试看起来像这样 Constants.py
:
FOO = 42
HOSTNAME=socket.gethostname()
if HOSTNAME == 'pudel':
BAR = 999
elif HOSTNAME == 'knork'
BAR = 888
else:
BAR = 777
虽然这样可以工作,但会让文件变得杂乱,因为里面有很多特殊情况,我想避免这种情况。
如果我在写 shell 脚本,我会用类似这样的方式 Constants.sh
:
FOO = 42
HOSTNAME=$(hostname)
BAR = 777
# load host-specific constants
if [ -e Constants_${HOSTNAME}.sh ]; then
. Constants_${HOSTNAME}.sh
fi
还有一个可选的 Constants_pudel.sh
,内容如下:
BAR = 999
这样可以把常用的常量放在一起,并且方便在不同的文件中覆盖它们。
但我现在不是在写 shell 脚本,而是在写 Python 程序,所以我想知道如何实现同样的效果。
我尝试了一些方法,比如:
FOO = 42
HOSTNAME=socket.gethostname()
BAR = 777
try:
__import__('Constants_'+HOSTNAME)
except ImportError:
pass
而 Constants_poodle.py
的内容会是:
import Constants
Constants.BAR = 999
这样在 Constants_poodle
内部是可以工作的,但当我在另一个 Python 文件中尝试 import Constants
时,得到的还是原来的 Constants.BAR
。
除了完全不工作之外,使用 __import__()
看起来也特别丑陋,所以我想知道有没有更合适的方法来为特定的设置覆盖导出的常量?
2 个回答
你可以用下面的方式来实现这个功能,这个方法来源于Activestate的一个关于Python常量的示例:Python中的常量
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
举个例子,如果_socket.gethostname()
返回了'pudel',并且有一个名为Constants_pudel
的文件,里面包含以下内容:
BAR = 999
FOO += 1
那么从print
语句输出的结果将会是:
43
pudel
999
你的解决方案有几个问题。首先,import 并不会把导入的模块添加到当前的命名空间里。我不太确定在 Python 3.x 中是怎样的,但在 Python 2.x 中,你可以这样做:
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
但是下一个问题是,所有的 constants_xxx.py 文件都必须在 Python 的路径中。
我认为一个更好的替代方案是把配置放在用户目录下的 .ini 文件中,然后使用 ConfigParser(或者根据你的喜好使用 yaml 或 xml)。