在运行时递归遍历Python继承树

9 投票
3 回答
4217 浏览
提问于 2025-04-16 01:44

我正在用Python写一些序列化和反序列化的代码,这段代码会从一些JSON数据中读取和写入继承层次结构。具体的结构在发送请求之前是无法确定的。

所以,我觉得一个优雅的解决方案是递归地检查Python类的层次结构,然后在返回的过程中,把正确的值放到Python的基本类型中。

E.g.,

A
|
|\
| \
B  C

如果我对B调用我的“检查”程序,它应该返回一个字典,这个字典包含了所有A的变量及其值,还有B的变量及其值。

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

那么,给定B(或C),我该如何获取A的 __slots____dict__ 呢?

我知道Python不像C++及其后代那样直接支持类型转换。

3 个回答

0

如果你只需要一个树形结构(而不是菱形继承),其实有个简单的方法可以做到。你可以用嵌套列表来表示这个树,分支用 [对象, [子节点]] 来表示,叶子节点用 [对象, [[]]] 来表示。

接下来,定义一个递归函数:

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__

在反序列化的时候,你需要从文本中恢复你的类。如果你想要更多帮助,请在你的问题中提供更多细节。

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()

如果你查看这个运行时对象的字典,你会发现它的变量和它的父类的变量没有区别。例如:

dir(b)
[ ... snip lots of double-underscores ... , 'x', 'y']

所以,直接回答你的问题是,它本来就是这样工作的,但我怀疑这对你帮助不大。没有显示出来的是方法,因为它们在类的命名空间中,而变量则在对象的命名空间中。如果你想找到父类中的方法,可以使用mro()这个调用,正如之前的回复中提到的,然后查看列表中类的命名空间。

在我寻找更简单的JSON序列化方法时,我在pickle模块中发现了一些有趣的东西。有一个建议是,你可能想要对对象进行pickle和unpickle,而不是自己写代码来遍历层级。pickle的输出是ASCII流,这样你可能更容易将其转换为JSON。PEP 307中有一些起点。

另一个建议是看看__reduce__方法,试试在你想要序列化的对象上,这可能正是你需要的。

13

你可以试试使用 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

撰写回答