【问题标题】:Recursively walking a Python inheritance tree at run-time在运行时递归遍历 Python 继承树
【发布时间】:2023-03-21 07:46:01
【问题描述】:

我正在用 Python 编写一些序列化/反序列化代码,这些代码将从一些 JSON 读取/写入继承层次结构。在发送请求之前,我们不会知道确切的组成。

因此,我认为递归内省要发出的 Python 类层次结构的优雅解决方案,然后在通过树返回的过程中,在 Python 基本类型中安装正确的值。

E.g.,

A
|
|\
| \
B  C

如果我在 B 上调用我的“内省”例程,它应该返回一个 dict,其中包含从 A 的所有变量到它们的值的映射,以及 B 的变量和它们的值。

就目前而言,我可以查看B.__slots__B.__dict__,但我只能从中提取B 的变量名。

仅给定 B,我如何获得 A 的 __slots__/__dict__? (或 C)。

我知道 python 不直接支持像 C++ 及其后代那样的强制转换-

【问题讨论】:

    标签: python reflection introspection


    【解决方案1】:

    您可以尝试使用 type.mro() 方法来查找方法解析顺序。

    class A(object):
            pass
    
    class B(A):
            pass
    
    class C(A):
            pass
    
    a = A()
    b = B()
    c = C()
    
    >>> type.mro(type(b))
    [<class '__main__.B'>, <class '__main__.A'>, <type 'object'>]
    >>> type.mro(type(c))
    [<class '__main__.C'>, <class '__main__.A'>, <type 'object'>]
    

    >>> type(b).mro()
    

    编辑:我以为你想做这样的事情......

    >>> A = type("A", (object,), {'a':'A var'})  # create class A
    >>> B = type("B", (A,), {'b':'B var'})       # create class B
    >>> myvar = B()
    
    def getvars(obj):
        ''' return dict where key/value is attribute-name/class-name '''
        retval = dict()
        for i in type(obj).mro():
            for k in i.__dict__:
                if not k.startswith('_'):
                    retval[k] = i.__name__
        return retval
    
    >>> getvars(myvar)
    {'a': 'A', 'b': 'B'}
    
    >>> for i in getvars(myvar):
        print getattr(myvar, i)   # or use setattr to modify the attribute value
    
    A Var
    B Var
    

    【讨论】:

    • 嗯,这表明我需要从返回列表的成员之一中向上转换。我不确定如何在 Python 中做到这一点。
    • 啊,我想我不明白你在寻找什么。我试图通过编辑来澄清我是如何解释你的问题的。
    • [local_variables_of(i()) for i in type(o).mro()[:-1]] 是我想出的解决方案。 local_variables_of 检查 slotsdict 并返回 attr 名称列表。
    【解决方案2】:

    也许你可以进一步澄清你在寻找什么?

    目前您的描述根本没有描述 Python。假设在您的示例中,A、B 和 C 是类的名称:

    class A(object) :
    ...     def __init__(self) :
    ...             self.x = 1
    class B(A) :
    ...     def __init__(self) :
    ...             A.__init__(self)
    ...             self.y = 1
    

    然后可以创建一个运行时实例:

    b = B()
    

    如果您查看运行时对象的字典,则它自己的变量和属于其超类的变量之间没有区别。例如: 目录(b)

    [ ... snip lots of double-underscores ... , 'x', 'y']
    

    因此,您的问题的直接答案是它已经像那样工作了,但我怀疑这对您没有太大帮助。没有显示的是方法,因为它们是类名称空间中的条目,而变量位于对象的名称空间中。如果您想在超类中查找方法,请使用前面回复中描述的 mro() 调用,然后查看列表中类的命名空间。

    当我四处寻找更简单的 JSON 序列化方法时,我在 pickle 模块中发现了一些有趣的东西。一个建议是,您可能想要腌制/取消腌制对象,而不是编写自己的对象来遍历层次结构。 pickle 输出是一个 ASCII 流,您可能更容易将它来回转换为 JSON。 PEP 307 中有一些起点。

    另一个建议是查看__reduce__ 方法,在您想要序列化的对象上尝试它,因为它可能是您正在寻找的。​​p>

    【讨论】:

    • 我正在做的是反序列化一个类层次结构,其中的对象组合成其他对象。成员对象需要移动到字典中。这被推送到json。当我对 slotsdict 变量进行分析时,我发现它们没有父变量。它们也没有指向父级的指针。
    【解决方案3】:

    如果您只需要一棵树(不是菱形继承),有一种简单的方法可以做到。用分支[object, [children]] 和叶子[object, [[]]] 的嵌套列表表示树。

    然后,通过定义递归函数:

    def classTree(cls): # return all subclasses in form of a tree (nested list)
        return [cls, [[b for c in cls.__subclasses__() for b in classTree(c)]]]
    

    可以得到继承树:

    class A():
        pass
    class B(A):
        pass
    class C(B):
        pass
    class D(C):
        pass
    class E(B):
        pass
    
    >>> classTree(A)
    [<class 'A'>, [[<class 'B'>, [[<class 'C'>, [[<class 'D'>, [[]]]], <class 'E'>, [[]]]]]]]
    

    这很容易序列化,因为它只是一个列表。如果您只想要名称,请将cls 替换为cls.__name__

    对于反序列化,您必须从文本中获取您的类。如果您需要更多帮助,请在您的问题中提供详细信息。

    【讨论】:

      猜你喜欢
      • 2021-05-21
      • 2018-07-12
      • 2014-03-11
      • 1970-01-01
      • 2017-11-28
      • 1970-01-01
      • 1970-01-01
      • 2017-05-11
      • 1970-01-01
      相关资源
      最近更新 更多