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