【问题标题】:overriding a set of namespaced constants覆盖一组命名空间常量
【发布时间】:2013-02-13 19:06:39
【问题描述】:

为了最小化整个程序中的硬编码值,我定义了一组常量,如下面的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__() 似乎特别难看,所以我想有一种适当的方法可以覆盖特定设置的导出常量?

【问题讨论】:

  • 这些常量可能取决于程序运行的实际主机。那么它们就不是常量了……
  • 好吧,我认为它们在给定的上下文中是不变的。但请随意编辑问题,为这些 constants 提供更好的措辞

标签: python constants overriding


【解决方案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,具体取决于您的喜好)。

【讨论】:

  • 我认为这就是我一直在寻找的... .ini 解决方案的加分项
【解决方案2】:

您可以使用以下代码执行类似操作,该代码源自 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  

【讨论】:

    猜你喜欢
    • 2020-07-03
    • 1970-01-01
    • 1970-01-01
    • 2021-06-27
    • 1970-01-01
    • 2010-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多