在运行时递归遍历Python继承树
我正在用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 个回答
如果你只需要一个树形结构(而不是菱形继承),其实有个简单的方法可以做到。你可以用嵌套列表来表示这个树,分支用 [对象, [子节点]]
来表示,叶子节点用 [对象, [[]]]
来表示。
接下来,定义一个递归函数:
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__
。
在反序列化的时候,你需要从文本中恢复你的类。如果你想要更多帮助,请在你的问题中提供更多细节。
你能再详细说明一下你想要什么吗?
目前你的描述根本没有提到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__
方法,试试在你想要序列化的对象上,这可能正是你需要的。
你可以试试使用 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