Python中的层次化数据结构与继承

huangapple go评论135阅读模式
英文:

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):
&quot;&quot;&quot; Metaclass that is instantiated once for each class &quot;&quot;&quot;
_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&#39;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):
&quot;&quot;&quot; Base class for each of the derived classes in our tree &quot;&quot;&quot;
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, &#39;_plural&#39;)
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 [&#39;fr&#39;, &#39;en&#39;]
self.lang = lang
class Submodule(ModuleBaseClass):
_plural = &#39;submodules&#39;
class Ability(ModuleBaseClass):
_plural = &#39;abilities&#39;
class Template(ModuleBaseClass):
_plural = &#39;templates&#39;
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(&#39;module1&#39;, &#39;fr&#39;)
sub_module1 = MySubmodule(&#39;sub_module1&#39;, module)
sub_module2 = MySubmodule(&#39;sub_module2&#39;, module)
ability = Ability(&#39;ability1&#39;, sub_module1)
template = Template(&#39;template1&#39;, ability)
print(template.lang)

I checked _metaclass_instances, it is [&lt;class &#39;NoneType&#39;&gt;, &lt;class &#39;__main__.Module&#39;&gt;, &lt;class &#39;__main__.Submodule&#39;&gt;, &lt;class &#39;__main__.Ability&#39;&gt;, &lt;class &#39;__main__.Template&#39;&gt;, &lt;class &#39;__main__.MyModule&#39;&gt;, &lt;class &#39;__main__.MySubmodule&#39;&gt;], 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:
&quot;&quot;&quot; Base class for each of the derived classes in our tree &quot;&quot;&quot;
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, &#39;_plural&#39;)
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.

huangapple
  • 本文由 发表于 2023年8月9日 16:20:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/76865855.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定