Я работал над макетом метакласса для проекта, в котором все классы используют собственный метакласс, который загружает заданную ими конфигурацию, а также конфигурацию родителей. По сути, каждый класс определяет вложенный класс Config
, который загружается в dict, тогда дети также могут определять его, и класс использует всю родительскую конфигурацию с любыми новыми значениями, перезаписанными.
Это прекрасно работает, когда я не удалите класс Config после загрузки его в dict, но теперь я пытаюсь реорганизовать и очистить пространство имен, и это вызывает проблемы. Новый (неработающий) код выглядит следующим образом:
class AbstractConfigMeta(ABCMeta):
"""Parse nested Config classes and fill a new classes config dict."""
def __new__(mcs, name, bases, namespace):
"""Traverse the MRO backwards from object to load all Config classes.
Any config declared in sub classes overwrites base classes.
"""
# get any config defined in parent classes first
config = {}
for parent in reversed(bases):
if hasattr(parent, "config"):
config.update(parent.config)
# pop Config class and add values if defined
config_class = namespace.pop("Config", None)
if config_class:
# get all non-magic (i.e. user-defined) attributes
attributes = {
key: value
for key, value in config_class.__dict__.items()
if not key.startswith("__")
}
config.update(attributes)
namespace["config"] = config
return super().__new__(mcs, name, bases, namespace)
, который анализирует класс Config
при использовании, но теперь не использует никакой конфигурации от родителей. Старый код, который работал, но сохранил вложенные классы после создания экземпляра:
class AbstractConfigMeta(ABCMeta):
"""Parse nested Config classes and fill a new classes config dict."""
def __new__(mcs, name, bases, namespace):
"""Traverse the MRO backwards from object to load all Config classes.
Any config declared in sub classes overwrites base classes.
"""
new_class = super().__new__(mcs, name, bases, namespace)
new_class.config = {} # type: ignore
for parent in reversed(new_class.__mro__):
config_class = getattr(parent, "Config", None)
if config_class:
# get all non-magic attributes from each Config class
values = {
key: value
for key, value in config_class.__dict__.items()
if not key.startswith("__")
}
new_class.config.update(values) # type: ignore
return new_class
Кажется, что теперь, пытаясь получить доступ к конфигурации, используя dict, созданный метаклассом, родительская конфигурация отбрасывается. Любая помощь будет принята с благодарностью.
Обновление
Проблема, как оказалось, вызвана некоторыми миксинами, которые используют вложенные классы Config, но не используют метакласс. Это было хорошо в старом блоке кода, но при переходе к получению родительской конфигурации из config config вместо вложенного класса, все, что не использует метакласс, не будет иметь этого определения, поэтому вместо этого есть класс Config, значения которого не используются.
Окончательный рабочий код, включая исправления и закрывающие случаи, предложенные jsbueno:
class AbstractConfigMeta(ABCMeta):
"""Parse nested Config classes and fill a new classes config dict."""
def __new__(mcs, name, bases, namespace):
"""Traverse the MRO backwards from object to load any config dicts.
Any Config class declared in sub classes overwrites parent classes.
"""
# pop Config class and add its attributes if defined
config_class = namespace.pop("Config", None)
if config_class:
# get all non-magic (i.e. user-defined) attributes
attributes = {
key: value
for key, value in config_class.__dict__.items()
if not key.startswith("__")
}
if namespace.get("config"):
warnings.warn(
f"A config dict and a config class are defined for {name}."
+ " Any values in the config dict will be overwritten."
)
namespace["config"] = attributes
new_class = super().__new__(mcs, name, bases, namespace)
# get any config dicts defined in the MRO (including the current class)
config = {}
for parent in reversed(new_class.__mro__):
if hasattr(parent, "config"):
config.update(parent.config) # type: ignore
new_class.config = config # type: ignore
return new_class