英文:
Python Hierarchical data structure with inheritance
问题
我想为一个分层数据结构创建一个元类,并编写一个供他人使用的框架。
我发现这个答案非常有帮助:https://codereview.stackexchange.com/a/162702/275475
它似乎接近解决我的问题,但是如果我继承了Submodule
类,它会在这一行显示断言错误:assert isinstance(parent, self._parent_class)
以下是代码(从上述链接中复制并添加了我的类定义):
class ModuleMetaClass(type):
"""每个类实例化一次的元类"""
_metaclass_instances = None
def __init__(cls, name, bases, attrs):
if cls._metaclass_instances is None:
# 第一个实例是ModuleBaseClass
cls._parent_class = None
cls._metaclass_instances = [type(None)]
else:
# 父类是先前声明的类
cls._parent_class = cls._metaclass_instances[-1]
# 如果不在树的顶部,那么我们是父类的子类
if cls._parent_class != type(None):
cls._parent_class._child_class = cls
# 将此类存储在类列表中
cls._metaclass_instances.append(cls)
# 没有子类
cls._child_class = None
# 调用基类(元类)的初始化方法
super().__init__(name, bases, attrs)
class ModuleBaseClass(metaclass=ModuleMetaClass):
"""树中每个派生类的基类"""
def __init__(self, name, parent):
assert isinstance(parent, self._parent_class)
self.name = name
self._parent = parent
if self._child_class is not None:
self._children = {}
# 子类变量的复数形式用于添加一个公共名称
plural = getattr(self._child_class, '_plural')
if plural is not None:
setattr(self, plural, self._children)
# 将自己添加到父类的集合中
if parent is not None:
parent._add_child(self)
# 为树中我们上面的每个节点添加一个访问属性
while parent is not None:
setattr(self, type(parent).__name__.lower(), parent)
parent = parent._parent
def _add_child(self, child):
assert isinstance(child, self._child_class)
assert child.name not in self._children
self._children[child.name] = child
# --------------------------------
class Module(ModuleBaseClass):
def __init__(self, name, lang):
super().__init__(name, None)
assert lang in ['fr', 'en']
self.lang = lang
class Submodule(ModuleBaseClass):
_plural = 'submodules'
class Ability(ModuleBaseClass):
_plural = 'abilities'
class Template(ModuleBaseClass):
_plural = 'templates'
def __init__(self, name, ability):
super().__init__(name, ability)
self.lang = module.lang
# --------------------------------
# 添加我的类定义
class MyModule(Module):
pass
class MySubmodule(Submodule):
pass
# --------------------------------
# 将实例化更改为我的类定义,然后它将显示断言错误。
module = MyModule('module1', 'fr')
sub_module1 = MySubmodule('sub_module1', module)
sub_module2 = MySubmodule('sub_module2', module)
ability = Ability('ability1', sub_module1)
template = Template('template1', ability)
print(template.lang)
我检查了_metaclass_instances
,它是[<class 'NoneType'>, <class '__main__.Module'>, <class '__main__.Submodule'>, <class '__main__.Ability'>, <class '__main__.Template'>, <class '__main__.MyModule'>, <class '__main__.MySubmodule'>]
,而MyModule
的父类是Template
,这显然是不正确的,但我不知道如何修改代码。
英文:
I would to like to create a metaclass for a hierarchical data structure and write a framework for others to use.
I found this answer very helpful: https://codereview.stackexchange.com/a/162702/275475
It seems close to solving my problem, but if I inherit, for example, Submodule
class, it will show assertion error at line: assert isinstance(parent, self._parent_class)
below is the code (copied from the answer in the link above and added my class defines)
class ModuleMetaClass(type):
""" Metaclass that is instantiated once for each class """
_metaclass_instances = None
def __init__(cls, name, bases, attrs):
if cls._metaclass_instances is None:
# First instance is ModuleBaseClass
cls._parent_class = None
cls._metaclass_instances = [type(None)]
else:
# parent class is the previously declared class
cls._parent_class = cls._metaclass_instances[-1]
# if not at the top of the tree, then we are our parent's child
if cls._parent_class != type(None):
cls._parent_class._child_class = cls
# store this class in the list of classes
cls._metaclass_instances.append(cls)
# no child class yet
cls._child_class = None
# call our base (meta) class init
super().__init__(name, bases, attrs)
class ModuleBaseClass(metaclass=ModuleMetaClass):
""" Base class for each of the derived classes in our tree """
def __init__(self, name, parent):
assert isinstance(parent, self._parent_class)
self.name = name
self._parent = parent
if self._child_class is not None:
self._children = {}
# child class variable plural is used to add a common name
plural = getattr(self._child_class, '_plural')
if plural is not None:
setattr(self, plural, self._children)
# add self to our parents collection
if parent is not None:
parent._add_child(self)
# add an access attribute for each of the nodes above us in the tree
while parent is not None:
setattr(self, type(parent).__name__.lower(), parent)
parent = parent._parent
def _add_child(self, child):
assert isinstance(child, self._child_class)
assert child.name not in self._children
self._children[child.name] = child
# --------------------------------
class Module(ModuleBaseClass):
def __init__(self, name, lang):
super().__init__(name, None)
assert lang in ['fr', 'en']
self.lang = lang
class Submodule(ModuleBaseClass):
_plural = 'submodules'
class Ability(ModuleBaseClass):
_plural = 'abilities'
class Template(ModuleBaseClass):
_plural = 'templates'
def __init__(self, name, ability):
super().__init__(name, ability)
self.lang = module.lang
# --------------------------------
# add my class defines
class MyModule(Module):
pass
class MySubmodule(Submodule):
pass
# --------------------------------
# changed instantiation to my class defines, then it would show the assertion error.
module = MyModule('module1', 'fr')
sub_module1 = MySubmodule('sub_module1', module)
sub_module2 = MySubmodule('sub_module2', module)
ability = Ability('ability1', sub_module1)
template = Template('template1', ability)
print(template.lang)
I checked _metaclass_instances
, it is [<class 'NoneType'>, <class '__main__.Module'>, <class '__main__.Submodule'>, <class '__main__.Ability'>, <class '__main__.Template'>, <class '__main__.MyModule'>, <class '__main__.MySubmodule'>]
, and MyModule
's parent class is Template
, this is obviously not correct, but I have no idea how to modify the code.
答案1
得分: 0
事实是,你正在尝试对不同的数据结构进行注释,这些数据结构不仅难以理解,而且期望线性继承(每个类只有一个子类),而实际上你有一个树形结构:一个类可以有多个子类。
事实上,Python 已经自动注释了所有或大部分的数据 - 在这种情况下根本不需要元类(即使在类创建时需要一些自定义注释,也可以使用 __init_subclass__
方法来满足需求)。
可以在类的 .__mro__
类属性中找到父类,以及在 .__subclasses__()
类方法中找到类的直接子类。如果你不想让最终的代码直接调用这些属性和方法,可以使用属性来获取你想要的值:
所以,尝试一下这个没有元类的版本 - 只需在链的末尾明确标记为 None
:
class ModuleBaseClass:
"""每个派生类的基类"""
def __init__(self, name, parent):
assert isinstance(parent, self._parent_class)
self.name = name
self._parent = parent
if self._child_classes:
self._children = {}
# 使用子类变量的复数形式添加一个常用名称
plural = getattr(self._child_class, '_plural')
if plural is not None:
setattr(self, plural, self._children)
# 将自己添加到父类的集合中
if parent is not None:
parent._add_child(self)
# 为树中的每个节点添加一个访问属性
while parent is not None:
setattr(self, type(parent).__name__.lower(), parent)
parent = parent._parent
@property
def _parent_class(self):
return type(self).__mro__[1]
@property
def _child_classes(self):
return tuple(type(self).__subclasses__())
def _add_child(self, child):
assert isinstance(child, self._child_classes)
assert child.name not in self._children
self._children[child.name] = child
def __getattr__(self, name):
try:
return self._children[name]
except KeyError as error:
raise AttributeError from error
def __len__(self):
return len(self.chilren)
我在这里添加了特殊的 __getattr__
和 __len__
方法,因为我认为它们可能对你的用例很方便。
英文:
The thing is that you are trying to annotate things in different data structures, that not only are hard to follow, but also expect a linear inheritance (only one child to each class), when you actually have a tree: one class can have multiple children.
The fact is that Python already annotates all, or most, of this data automatically - there is no need for a metaclass at all in this case (and even if there is some custom annotation needed at class creation time, a __init_subclass__
method should suffice in this case.
Parent classes to a class can be found in the .__mro__
class attribute, and immediate subclasses to a class in the .__subclasses__()
class method. If you don't want your final code to call those directly, you can use properties to get the values you want:
So, try this instead, without a metaclass - give or take marking the ends of your chain explicitly with a None
:
class ModuleBaseClass:
""" Base class for each of the derived classes in our tree """
def __init__(self, name, parent):
assert isinstance(parent, self._parent_class)
self.name = name
self._parent = parent
if self._child_classes:
self._children = {}
# child class variable plural is used to add a common name
plural = getattr(self._child_class, '_plural')
if plural is not None:
setattr(self, plural, self._children)
# add self to our parents collection
if parent is not None:
parent._add_child(self)
# add an access attribute for each of the nodes above us in the tree
while parent is not None:
setattr(self, type(parent).__name__.lower(), parent)
parent = parent._parent
@property
def _parent_class(self):
return type(self).__mro__[1]
@property
def _child_classes(self):
return tuple(type(self).__subclasses__())
def _add_child(self, child):
assert isinstance(child, self._child_classes)
assert child.name not in self._children
self._children[child.name] = child
def __getattr__(self, name):
try:
return self._children[name]
except KeyError as error:
raise AttributeError from error
def __len__(self):
return len(self.chilren)
I added the special __getattr__
and __len__
calls there, as I think they may be convenient to your use case.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论