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

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

Python Hierarchical data structure with inheritance

问题

我想为一个分层数据结构创建一个元类,并编写一个供他人使用的框架。

我发现这个答案非常有帮助:https://codereview.stackexchange.com/a/162702/275475
它似乎接近解决我的问题,但是如果我继承了Submodule类,它会在这一行显示断言错误:assert isinstance(parent, self._parent_class)

以下是代码(从上述链接中复制并添加了我的类定义):

  1. class ModuleMetaClass(type):
  2. """每个类实例化一次的元类"""
  3. _metaclass_instances = None
  4. def __init__(cls, name, bases, attrs):
  5. if cls._metaclass_instances is None:
  6. # 第一个实例是ModuleBaseClass
  7. cls._parent_class = None
  8. cls._metaclass_instances = [type(None)]
  9. else:
  10. # 父类是先前声明的类
  11. cls._parent_class = cls._metaclass_instances[-1]
  12. # 如果不在树的顶部,那么我们是父类的子类
  13. if cls._parent_class != type(None):
  14. cls._parent_class._child_class = cls
  15. # 将此类存储在类列表中
  16. cls._metaclass_instances.append(cls)
  17. # 没有子类
  18. cls._child_class = None
  19. # 调用基类(元类)的初始化方法
  20. super().__init__(name, bases, attrs)
  21. class ModuleBaseClass(metaclass=ModuleMetaClass):
  22. """树中每个派生类的基类"""
  23. def __init__(self, name, parent):
  24. assert isinstance(parent, self._parent_class)
  25. self.name = name
  26. self._parent = parent
  27. if self._child_class is not None:
  28. self._children = {}
  29. # 子类变量的复数形式用于添加一个公共名称
  30. plural = getattr(self._child_class, '_plural')
  31. if plural is not None:
  32. setattr(self, plural, self._children)
  33. # 将自己添加到父类的集合中
  34. if parent is not None:
  35. parent._add_child(self)
  36. # 为树中我们上面的每个节点添加一个访问属性
  37. while parent is not None:
  38. setattr(self, type(parent).__name__.lower(), parent)
  39. parent = parent._parent
  40. def _add_child(self, child):
  41. assert isinstance(child, self._child_class)
  42. assert child.name not in self._children
  43. self._children[child.name] = child
  44. # --------------------------------
  45. class Module(ModuleBaseClass):
  46. def __init__(self, name, lang):
  47. super().__init__(name, None)
  48. assert lang in ['fr', 'en']
  49. self.lang = lang
  50. class Submodule(ModuleBaseClass):
  51. _plural = 'submodules'
  52. class Ability(ModuleBaseClass):
  53. _plural = 'abilities'
  54. class Template(ModuleBaseClass):
  55. _plural = 'templates'
  56. def __init__(self, name, ability):
  57. super().__init__(name, ability)
  58. self.lang = module.lang
  59. # --------------------------------
  60. # 添加我的类定义
  61. class MyModule(Module):
  62. pass
  63. class MySubmodule(Submodule):
  64. pass
  65. # --------------------------------
  66. # 将实例化更改为我的类定义,然后它将显示断言错误。
  67. module = MyModule('module1', 'fr')
  68. sub_module1 = MySubmodule('sub_module1', module)
  69. sub_module2 = MySubmodule('sub_module2', module)
  70. ability = Ability('ability1', sub_module1)
  71. template = Template('template1', ability)
  72. 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)

  1. class ModuleMetaClass(type):
  2. &quot;&quot;&quot; Metaclass that is instantiated once for each class &quot;&quot;&quot;
  3. _metaclass_instances = None
  4. def __init__(cls, name, bases, attrs):
  5. if cls._metaclass_instances is None:
  6. # First instance is ModuleBaseClass
  7. cls._parent_class = None
  8. cls._metaclass_instances = [type(None)]
  9. else:
  10. # parent class is the previously declared class
  11. cls._parent_class = cls._metaclass_instances[-1]
  12. # if not at the top of the tree, then we are our parent&#39;s child
  13. if cls._parent_class != type(None):
  14. cls._parent_class._child_class = cls
  15. # store this class in the list of classes
  16. cls._metaclass_instances.append(cls)
  17. # no child class yet
  18. cls._child_class = None
  19. # call our base (meta) class init
  20. super().__init__(name, bases, attrs)
  21. class ModuleBaseClass(metaclass=ModuleMetaClass):
  22. &quot;&quot;&quot; Base class for each of the derived classes in our tree &quot;&quot;&quot;
  23. def __init__(self, name, parent):
  24. assert isinstance(parent, self._parent_class)
  25. self.name = name
  26. self._parent = parent
  27. if self._child_class is not None:
  28. self._children = {}
  29. # child class variable plural is used to add a common name
  30. plural = getattr(self._child_class, &#39;_plural&#39;)
  31. if plural is not None:
  32. setattr(self, plural, self._children)
  33. # add self to our parents collection
  34. if parent is not None:
  35. parent._add_child(self)
  36. # add an access attribute for each of the nodes above us in the tree
  37. while parent is not None:
  38. setattr(self, type(parent).__name__.lower(), parent)
  39. parent = parent._parent
  40. def _add_child(self, child):
  41. assert isinstance(child, self._child_class)
  42. assert child.name not in self._children
  43. self._children[child.name] = child
  44. # --------------------------------
  45. class Module(ModuleBaseClass):
  46. def __init__(self, name, lang):
  47. super().__init__(name, None)
  48. assert lang in [&#39;fr&#39;, &#39;en&#39;]
  49. self.lang = lang
  50. class Submodule(ModuleBaseClass):
  51. _plural = &#39;submodules&#39;
  52. class Ability(ModuleBaseClass):
  53. _plural = &#39;abilities&#39;
  54. class Template(ModuleBaseClass):
  55. _plural = &#39;templates&#39;
  56. def __init__(self, name, ability):
  57. super().__init__(name, ability)
  58. self.lang = module.lang
  59. # --------------------------------
  60. # add my class defines
  61. class MyModule(Module):
  62. pass
  63. class MySubmodule(Submodule):
  64. pass
  65. # --------------------------------
  66. # changed instantiation to my class defines, then it would show the assertion error.
  67. module = MyModule(&#39;module1&#39;, &#39;fr&#39;)
  68. sub_module1 = MySubmodule(&#39;sub_module1&#39;, module)
  69. sub_module2 = MySubmodule(&#39;sub_module2&#39;, module)
  70. ability = Ability(&#39;ability1&#39;, sub_module1)
  71. template = Template(&#39;template1&#39;, ability)
  72. 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

  1. class ModuleBaseClass:
  2. """每个派生类的基类"""
  3. def __init__(self, name, parent):
  4. assert isinstance(parent, self._parent_class)
  5. self.name = name
  6. self._parent = parent
  7. if self._child_classes:
  8. self._children = {}
  9. # 使用子类变量的复数形式添加一个常用名称
  10. plural = getattr(self._child_class, '_plural')
  11. if plural is not None:
  12. setattr(self, plural, self._children)
  13. # 将自己添加到父类的集合中
  14. if parent is not None:
  15. parent._add_child(self)
  16. # 为树中的每个节点添加一个访问属性
  17. while parent is not None:
  18. setattr(self, type(parent).__name__.lower(), parent)
  19. parent = parent._parent
  20. @property
  21. def _parent_class(self):
  22. return type(self).__mro__[1]
  23. @property
  24. def _child_classes(self):
  25. return tuple(type(self).__subclasses__())
  26. def _add_child(self, child):
  27. assert isinstance(child, self._child_classes)
  28. assert child.name not in self._children
  29. self._children[child.name] = child
  30. def __getattr__(self, name):
  31. try:
  32. return self._children[name]
  33. except KeyError as error:
  34. raise AttributeError from error
  35. def __len__(self):
  36. 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:

  1. class ModuleBaseClass:
  2. &quot;&quot;&quot; Base class for each of the derived classes in our tree &quot;&quot;&quot;
  3. def __init__(self, name, parent):
  4. assert isinstance(parent, self._parent_class)
  5. self.name = name
  6. self._parent = parent
  7. if self._child_classes:
  8. self._children = {}
  9. # child class variable plural is used to add a common name
  10. plural = getattr(self._child_class, &#39;_plural&#39;)
  11. if plural is not None:
  12. setattr(self, plural, self._children)
  13. # add self to our parents collection
  14. if parent is not None:
  15. parent._add_child(self)
  16. # add an access attribute for each of the nodes above us in the tree
  17. while parent is not None:
  18. setattr(self, type(parent).__name__.lower(), parent)
  19. parent = parent._parent
  20. @property
  21. def _parent_class(self):
  22. return type(self).__mro__[1]
  23. @property
  24. def _child_classes(self):
  25. return tuple(type(self).__subclasses__())
  26. def _add_child(self, child):
  27. assert isinstance(child, self._child_classes)
  28. assert child.name not in self._children
  29. self._children[child.name] = child
  30. def __getattr__(self, name):
  31. try:
  32. return self._children[name]
  33. except KeyError as error:
  34. raise AttributeError from error
  35. def __len__(self):
  36. 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:

确定