Python 有没有一种方法可以动态更新子类中的变量?

Python 有没有一种方法可以动态更新子类中的变量?,python,python-3.x,flask,Python,Python 3.x,Flask,我有两个配置类,基类config和子类ProdConfig,代码如下所示: 类配置: URL=”http://127.0.0.1" 另一个URL=URL+“/home” 类ProdConfig(配置): URL=”http://test.abc.com" #另一个URL=URL+“/home” 打印(Config.URL)#”http://127.0.0.1" 打印(配置另一个URL)#“http://127.0.0.1/home" 打印(ProdConfig.URL)#”http://test

我有两个配置类,基类
config
和子类
ProdConfig
,代码如下所示:

类配置:
URL=”http://127.0.0.1"
另一个URL=URL+“/home”
类ProdConfig(配置):
URL=”http://test.abc.com"
#另一个URL=URL+“/home”
打印(Config.URL)#”http://127.0.0.1"
打印(配置另一个URL)#“http://127.0.0.1/home"
打印(ProdConfig.URL)#”http://test.abc.com"
打印(ProdConfig.other_URL)#http://127.0.0.1/home
如果我没有在ProdConfig中重写变量
另一个_URL
,或者将其声明为
@property
,则该值与基类相同,我得到它的原因是在导入基类时分配了值,但我可以用新值将其与新
URL
对齐吗?有没有办法使用
元类
setattr
getattr
技巧来解决此问题


多谢各位

如问题和随后的讨论所述,
属性
的使用不能直接用于类定义,强制子类也定义属性以维护每个属性的定义“协议”可能会变得很麻烦。还建议使用格式字符串,但对于标准
类型
元类在类定义中完成的赋值,我们仍然存在相同的问题,即它不会被重新计算。考虑另一种方法:

class Config:
    URL = "http://127.0.0.1"
    ANOTHER_URL = f"{URL}/home"

class ProdConfig(Config):
    URL = "http://test.abc.com"
运行以下操作将无法产生所需的结果:

>>> conf = ProdConfig()                           
>>> print(conf.URL)                               
http://test.abc.com
>>> print(conf.ANOTHER_URL)                       
http://127.0.0.1/home
只是因为
另一个_URL
没有在
ProdConfig
的范围内重新分配。但是,可以使用以下元类解决此问题:

class ConfigFormatMeta(type):

    def __init__(cls, name, bases, attrs):
        # create and store a "private" mapping of original definitions,
        # for reuse by subclasses
        cls._config_map = config_map = {}
        # merge all config_maps of base classes.
        for base_cls in bases:
            if hasattr(base_cls, '_config_map'):
                config_map.update(base_cls._config_map)

        # update the config_map with original definitions in the newly
        # constructed class, filter out all values beginning with '_'
        config_map.update({
            k: v for k, v in vars(cls).items() if not k.startswith('_')})

        # Now assign the formatted attributes to the class
        for k in config_map:
            # Only apply to str attributes; other types of attributes
            # on the class will need additional work.
            if isinstance(config_map[k], str):
                setattr(cls, k, config_map[k].format(**config_map))

        super().__init__(name, bases, attrs)
class Config(metaclass=ConfigFormatMeta):
    URL = 'http://example.com'
    ANOTHER_URL = '{URL}/home'


class ProdConfig(Config):
    URL = 'http://abc.example.com'
这样,请尝试使用新元类将
Config
类作为基础:

class ConfigFormatMeta(type):

    def __init__(cls, name, bases, attrs):
        # create and store a "private" mapping of original definitions,
        # for reuse by subclasses
        cls._config_map = config_map = {}
        # merge all config_maps of base classes.
        for base_cls in bases:
            if hasattr(base_cls, '_config_map'):
                config_map.update(base_cls._config_map)

        # update the config_map with original definitions in the newly
        # constructed class, filter out all values beginning with '_'
        config_map.update({
            k: v for k, v in vars(cls).items() if not k.startswith('_')})

        # Now assign the formatted attributes to the class
        for k in config_map:
            # Only apply to str attributes; other types of attributes
            # on the class will need additional work.
            if isinstance(config_map[k], str):
                setattr(cls, k, config_map[k].format(**config_map))

        super().__init__(name, bases, attrs)
class Config(metaclass=ConfigFormatMeta):
    URL = 'http://example.com'
    ANOTHER_URL = '{URL}/home'


class ProdConfig(Config):
    URL = 'http://abc.example.com'
现在再试一次:

>>> conf = ProdConfig()
>>> print(conf.URL)
http://abc.example.com
>>> print(conf.ANOTHER_URL)
http://abc.example.com/home
请注意,
ProdConfig
的范围中没有重新定义另一个URL,但是只具有
URL
的期望行为被重新定义为具有
的期望值http://abc.example.com/home“
已实现


另外值得注意的是,使用元类会干扰,并且与原始映射有一些重复,这些重复通过类本身的
\u config\u map
属性略微隐藏,因此,请主要将其视为概念证明。

如果您想继承站点的默认主目录,但也可以在创建子类实例时覆盖它。你可以这样做

这显示了子类如何继承默认状态,或根据传递给构造函数的参数自定义其状态

>>> class Config:
...     LOCALHOST    = "http://127.0.0.1"
...     DEFAULT_HOME = "/home"
...     def __init__(self):
...         self._home = self.DEFAULT_HOME
... 
...     @property
...     def url(self):
...         return self.LOCALHOST + self.home
... 
...     @property
...     def home(self):
...         return self._home
... 
...     @home.setter
...     def home(self, h):
...         self._home = h
...         
>>> class ProdConfig(Config):
... 
...     def __init__(self, home=None):
...         super().__init__()
...         if home:
...             self.home = home
>>> 
>>> c = Config()
>>> d = ProdConfig()
>>> e = ProdConfig("/someotherhome")
>>> 
>>> c.url
'http://127.0.0.1/home'
>>> d.url
'http://127.0.0.1/home'
>>> e.url
'http://127.0.0.1/someotherhome'
>>> 

我上面的想法是展示一个良好的实践,提供子类对其继承状态的访问,而不需要直接敏感地访问基类的变量。私有
\u home
基类变量由子类通过属性访问。使用另一种语言,我可能会声明
home
受保护的
属性,而
\u home
private

proerty
定义,你是指
@property
装饰者吗?当在类级别访问
另一个\u URL
的赋值时,即使这样也不能解决您的问题-一个
属性
通常只对类的实例产生影响(即访问
ProdConfig。另一个_URL
将返回那里的精确赋值,这将是
属性
对象定义,而不是应用程序可能期望的值).Metaclass可能有帮助,但其额外的复杂性可能会导致额外的复杂性。在任何情况下,如果您正在考虑使用元类实现类属性,则有一个解决方案,但请注意所需的额外复杂性。是的,谢谢,它是
@property
,我编辑了它。尽管它不会直接解决我的问题,但我可以初始化
Config
ProdConfig
,然后将其用作一个对象,也许它作为一个配置类很难看,我想,然后简单地定义一个名为
的函数,另一个URL
返回self.URL+'/home'
,并用
@property
装饰器装饰它。是的,它可能很好,但我认为它不是谨慎地在配置类中编写方法,如果我重写子类中的字段,我认为这太多的编写是不必要的,所以这就是我混淆的地方。。。