有没有办法动态更新子类中的变量?

时间:2020-02-20 01:05:32

标签: python python-3.x flask

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

class Config:
    URL = "http://127.0.0.1"
    ANOTHER_URL = URL + "/home"

class ProdConfig(Config):
    URL = "http://test.abc.com"
    # ANOTHER_URL = URL + "/home"

print(Config.URL) # "http://127.0.0.1"
print(Config.ANOTHER_URL) # "http://127.0.0.1/home"
print(ProdConfig.URL) # "http://test.abc.com"
print(ProdConfig.ANOTHER_URL) # http://127.0.0.1/home

如果我没有在ProdConfig中覆盖变量ANOTHER_URL或将其声明为@property,则该值与基类相同,我知道在导入基类时已分配了cus值,但是我可以以新值获取它并与新URL对齐?有没有办法使用Metaclasssetattrgetattr的技巧来解决此问题?

非常感谢您!

2 个答案:

答案 0 :(得分:1)

如该问题及其所附的讨论中所述,property的使用不能直接在类定义上使用,并且迫使子类也必须定义属性以维护以下内容的“协议”每个属性都可能变得麻烦。还建议使用格式字符串,但是对于标准type元类,在类定义时进行的赋值仍然存在相同的问题,那就是它不会被重新计算。考虑另一种方法:

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

仅由于未在ANOTHER_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)

然后,尝试使用新的元类将Config类作为基础:

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

请注意,ANOTHER_URL并未在ProdConfig的范围内重新定义,但是仅将URL重新定义为具有预期的'http://abc.example.com/home'的期望行为是实现。

另外,值得注意的是,使用元类会干扰multiple inheritance with other classes that have a different base metaclass,并且与原始映射有些重复,而该映射通过类本身的_config_map属性被稍微隐藏了,因此请主要将其视为概念的证明。

答案 1 :(得分:0)

如果您想要一种方法来继承站点的默认主目录,但还可以在创建子类的实例时覆盖它。您可以通过这种方式完成。

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

>>> 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'
>>> 

我在上面的想法是展示一种良好的做法,即为子类提供对其继承状态的访问,而无需直接对基类的变量进行敏感访问。私有_home基类变量由子类通过属性访问。使用另一种语言,我可能会将home声明为protected属性,将<base>._home声明为private