Python 如何实现复合配置变量,或;模块级属性?“;

Python 如何实现复合配置变量,或;模块级属性?“;,python,python-2.7,Python,Python 2.7,如果我有一个配置文件,default.py: HOST = 'localhost' PORT = 8080 PROTOCOL = 'http' if PORT != 443 else 'https' ROOT_STR = '%s://%s:%s' % (PROTOCOL, HOST, PORT) 然后我有一个local.py,它覆盖默认变量以适应本地环境: PORT = 443 我如何动态地重新生成协议和根目录,就好像它们是类的属性一样,而是在模块的根目录级别 通过更改一个变量得到的配置将为

如果我有一个配置文件,
default.py

HOST = 'localhost'
PORT = 8080
PROTOCOL = 'http' if PORT != 443 else 'https'
ROOT_STR = '%s://%s:%s' % (PROTOCOL, HOST, PORT)
然后我有一个
local.py
,它覆盖默认变量以适应本地环境:

PORT = 443
我如何动态地重新生成
协议
根目录
,就好像它们是类的属性一样,而是在模块的根目录级别

通过更改一个变量得到的配置将为:

HOST = 'localhost'
PORT = 443
PROTOCOL = 'https'
ROOT_STR = 'https://localhost:443'
预期行为应为类属性建模:

class Configuration(object):
    def __init__(self):
        self.host = 'localhost'
        self.port = 8080

    @property
    def protocol(self):
        return 'https' if self.port == 443 else 'http'

    @property
    def root_str(self):
        return '%s://%s:%s' % (self.protocol, self.host, self.port)

...
...

>>> c = Configuration()
>>> c.port = 443
>>> c.root_str
'https://localhost:443'
有没有比修改AST更干净的方法


想法?

您可以将默认导入中的
添加到
local.py
文件的顶部。这将使
default.py
中的所有变量和函数都可用,就像它们在
local.py
中一样。我使用了一个自定义配置
类和
元类,大致实现如下

import re
import types

CONST_VARS = re.compile(r'[A-Z]+[_A-Z]*')

# Configuration primitives that allow for dynamic
# variable compilation for default/local/dev/etc settings files.
class ConfigType(type):
    def __init__(cls, name, bases, odict):
        if not hasattr(cls, 'vars'):
            cls.vars = {}
        reg_vars = filter(lambda x: CONST_VARS.match(x[0]), odict.items())
        for k, v in reg_vars:
            cls.vars[k] = v
            delattr(cls, k)
        super(ConfigType, cls).__init__(name, bases, cls.vars)


class Configuration(object):
    __metaclass__ = ConfigType

    def __init__(self):
        pass
    def __setattr__(self, key, value):
        self.vars[key] = value
    def __getattr__(self, item):
        ret = self.vars.get(item)
        if isinstance(ret, types.FunctionType):
            return ret(self)
        return ret
    def as_module(self):
        mod_vars = {}
        for k, v in self.vars.items():
            if isinstance(v, types.FunctionType) and callable(v):
                v = v(self)
            mod_vars[k] = v
        return type('Configuration', (object,), mod_vars)
然后,如果我创建两个
Configuration
对象:

设置/default.py

class DatabaseConfig(Configuration):
    MYSQL_DB = 'DEFAULT'
    MYSQL_USER = os.environ.get('MYSQL_USER')
    MYSQL_PASS = os.environ.get('MYSQL_PASS')
    MYSQL_HOST = os.environ.get('MYSQL_HOST', 'localhost')
    MYSQL_PORT = os.environ.get('MYSQL_PORT', 3306)

    def MYSQL_URI(self):
        return 'mysql://{user}:{password}@{server}:{port}'.format(
            user=self.MYSQL_USER,
            password=self.MYSQL_PASS,
            server=self.MYSQL_HOST,
            port=self.MYSQL_PORT )

    def SQLALCHEMY_DATABASE_URI(self):
        return str(self.MYSQL_URI) + '/' + self.MYSQL_DB

DatabaseConfig()
class LocalConfig(Configuration):
    MYSQL_DB = 'mycoolstuff'
    MYSQL_USER = 'root'
    MYSQL_PASS = 'rootpass'
    MYSQL_HOST = '172.17.42.1'  # Docker
    MYSQL_PORT = 3306

Config = LocalConfig()
设置/local.py

class DatabaseConfig(Configuration):
    MYSQL_DB = 'DEFAULT'
    MYSQL_USER = os.environ.get('MYSQL_USER')
    MYSQL_PASS = os.environ.get('MYSQL_PASS')
    MYSQL_HOST = os.environ.get('MYSQL_HOST', 'localhost')
    MYSQL_PORT = os.environ.get('MYSQL_PORT', 3306)

    def MYSQL_URI(self):
        return 'mysql://{user}:{password}@{server}:{port}'.format(
            user=self.MYSQL_USER,
            password=self.MYSQL_PASS,
            server=self.MYSQL_HOST,
            port=self.MYSQL_PORT )

    def SQLALCHEMY_DATABASE_URI(self):
        return str(self.MYSQL_URI) + '/' + self.MYSQL_DB

DatabaseConfig()
class LocalConfig(Configuration):
    MYSQL_DB = 'mycoolstuff'
    MYSQL_USER = 'root'
    MYSQL_PASS = 'rootpass'
    MYSQL_HOST = '172.17.42.1'  # Docker
    MYSQL_PORT = 3306

Config = LocalConfig()
当我导入
LocalConfig
时,我的变量将自动计算,所有函数作为属性,所有类属性作为变量

>>> import settings.default
>>> from settings.local import Config
>>> Config.MYSQL_PORT
3306
>>> Config.MYSQL_URI
mysql://root:rootpass@172.17.42.1:3306

@RNar可能重复不幸的是,它们不同-问题不是访问变量,而是动态地重新生成后续变量,这些变量在赋值中使用了更改的变量。我不太明白您想尝试什么,但是让
local.py
只在本地存在是否更有意义,然后使用一个
try…除了importorror
之外,如果存在
local.py
值,则导入该值,否则保留全局默认值?如果范围和限制得当,甚至可能是
导入*
的罕见好用例之一。@Two Bitalchest我用一个类级属性更新了这个问题,以演示预期的行为。您提供的类似单例的类是否适合您的要求?这种“类型”有效,但是所有计算的变量都不会根据
local.py中的更改重新计算。例如:如果
C=A+B
default
中,其中
A
B
也被定义,如果我在
local
中更改
A
,它还应该更新
C
的值,这是非常正确的。一种方法是使用一个可以扩展的类(
class BaseSettings(object):…
在默认情况下,然后是底部的
SETTINGS=BaseSettings()
,然后是
class LocalSettings(BaseSettings):…
在本地文件中导入后,然后是
SETTINGS=LocalSettings()
)或者将动态逻辑放入一个管理器中,通过它您可以使用这些设置。可能还有更多的方法。