0

为了在整个程序中最小化硬编码值,我定义了一组常量,如下所示Constants.py

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

所有想要使用这些常量的模块,只需执行import ConstantsConstants.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但当我尝试import Constants在另一个 python 文件中时,我得到了原始的Constants.BAR.

除了根本不工作之外,使用__import__()似乎特别难看,所以我想有一种适当的方法可以覆盖特定设置的导出常量?

4

2 回答 2

1

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  
于 2013-02-13T21:20:30.877 回答
1

您的解决方案有几个问题。首先,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,具体取决于您的喜好)。

于 2013-02-13T20:17:40.243 回答