[英]Issues with using python metaclasses and inheritence
我一直在为一个项目研究元类布局,其中所有类都使用一个自定义元类来加载他们定义的配置以及父类的配置。 基本上每个 class 定义一个嵌套的Config
class 加载到一个字典,然后孩子也可以定义一个 class 使用所有父配置并覆盖任何新值。
当我在将其加载到字典后不删除 Config class 时,它工作得很好,但现在我正在尝试重构和清理命名空间,它会导致问题。 新的(坏掉的)代码如下:
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 但现在不使用来自父母的任何配置。 在实例化后有效但保留嵌套类的旧代码是:
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
现在似乎尝试使用元类创建的字典访问配置,父配置被丢弃。 任何帮助将非常感激。
这个问题原来是由一些使用嵌套 Config 类但不使用元类的 Mixins 引起的。 这在旧代码块中很好,但是当更改为从配置字典而不是嵌套的 class 获取父配置时,任何不使用元类的东西都不会定义这个,因此有一个未使用值的配置 class。
最终的工作代码,包括 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
问题在于,在新代码中,您通过 class 显式bases
进行交互,而旧(工作)代码迭代__mro__
。
bases
只会产生显式声明的祖先,并且不会访问任何“祖父母”或更复杂层次结构中的类。
The way to go is to allow Python generate the __mro__
by actually creating your new class, and iterating to retrieve your config keys on the new class. config
属性可以只在新创建的 class 上设置 - 无需在命名空间中执行此操作。
不建议尝试复制 Python 的__mro__
——这是一个相当复杂的算法,即使你一步一步地做对了,你也只是在重新发明轮子。
所以,有一些东西:
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.
"""
config_class = namespace.pop("Config", None)
cls = super().__new__(mcs, name, bases, namespace)
# get any config defined in parent classes first
config = {}
for parent in reversed(cls.__mro__):
# Keep in mind this also runs for `cls` itself, so "config" can
# also be specced as a dictionary. If you don't want that
# to be possible, place a condition here to raise if `parent is cls and hasattr...`
if hasattr(parent, "config"):
config.update(parent.config)
# pop Config class and add values if defined
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)
cls.config = config
return cls
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.