Есть ли способ динамически обновлять переменные c в подклассе? - PullRequest
1 голос
/ 20 февраля 2020

У меня есть два класса конфигурации, базовый класс 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

Если я не переопределить переменную ANOTHER_URL в ProdConfig или объявить ее как @property, значение совпадает с базовым классом, я понял, что это значение назначается при импорте базового класса, но могу ли я получить его с новым значением, выровненным по новому URL? Есть ли способ использовать Metaclass или setattr или getattr трюки для решения этой проблемы ??

Большое спасибо!

Ответы [ 2 ]

1 голос
/ 20 февраля 2020

Как отмечено в вопросе и последующем обсуждении, прилагаемом к нему, использование 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'.

Также стоит отметить, что использование метаклассов будет мешать множественному наследованию с другими классами, имеющими другой базовый метакласс , и что есть небольшое дублирование с оригинальным отображением, которое немного скрыт через атрибут _config_map в самом классе, так что, пожалуйста, рассматривайте это в основном как подтверждение концепции.

0 голосов
/ 20 февраля 2020

Если вам нужен способ наследовать домашний каталог по умолчанию вашего сайта, но вы также можете переопределить его при создании экземпляров подкласса. Вы можете сделать это следующим образом.

Это показывает, как подкласс может наследовать состояние по умолчанию или настраивать его состояние в зависимости от параметра, передаваемого конструктору.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...