【问题标题】:Is there a way to dynamic update variables in subclass?有没有办法动态更新子类中的变量?
【发布时间】:2020-06-04 06:46:46
【问题描述】:

我有两个配置类,基类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 值是在导入基类时分配的,但我可以用 new 得到它值与新的URL 对齐?有没有办法使用Metaclasssetattrgetattr技巧来解决这个问题??

非常感谢!

【问题讨论】:

  • proerty 是指@property 装饰器吗?在类级别访问 ANOTHER_URL 的分配时,即使这样也无法解决您的问题 - property 通常只会对类的实例产生影响(即访问 ProdConfig.ANOTHER_URL 将返回那里的确切分配,这将 property 对象定义,而不是您的应用程序可能期望的值)。元类可能对此有所帮助,但其额外的复杂性可能会导致额外的复杂性。
  • 无论如何,如果您正在考虑使用元类来实现类属性,this thread 有一个解决方案,但请注意所需的额外复杂性。
  • 是的,谢谢,它是@property,我编辑了它。虽然它不会直接解决我的问题,但我可以初始化ConfigProdConfig 然后将其用作对象,也许我认为它作为 Config 类很难看
  • 然后简单地定义一个名为ANOTHER_URL的函数return self.URL + '/home'并用@property装饰器装饰它。
  • 是的,可能没问题,但是我觉得在config类中写方法不太好看,如果我在子类中覆盖字段,我认为写太多了没有必要,所以这就是我的困惑......

标签: python python-3.x flask


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    正如问题和所附的以下讨论中所述,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 属性略微隐藏的原始映射有一些重复,因此请多处理作为概念证明。

    【讨论】:

      猜你喜欢
      • 2020-01-22
      • 1970-01-01
      • 2021-02-20
      • 2016-05-31
      • 2021-10-22
      • 1970-01-01
      • 2021-09-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多