覆盖一组命名空间常量

时间:2013-02-13 19:06:39

标签: python constants overrides

为了最小化整个程序中的硬编码值,我定义了一组常量,如下面的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 个答案:

答案 0 :(得分:1)

您的解决方案有几个问题。首先,导入不会将导入的模块添加到当前名称空间。不确定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,具体取决于您的口味)。

答案 1 :(得分:1)

您可以使用以下内容执行类似操作,该操作源自Activestate Constants in 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