利用 graphviz 可视化 Python MRO 层级关系

graphviz

graphviz 是一个很著名的绘图工具,参考

graphviz 有一些 Python 的封装,通过 pip install graphviz 可以安装

In [1]:
from graphviz import Digraph

dot = Digraph(comment='Flask')
dot.node('A', 'Python')
dot.node('B', 'Werkzeug')
dot.node('C', 'Flask')
dot.edges(['AB', 'AC'])
dot.edge('B', 'C', constraint='false')
dot
Out[1]:
%3 A Python B Werkzeug A->B C Flask A->C B->C

Python MRO

所谓 MRO 就是 Method Resolution Order,Python 的类通过 cls.__mro__ 方法可以查看 MRO

参考:https://www.python.org/download/releases/2.3/mro/

In [2]:
# Add test class, noqa please
class A(object): pass
class B(A): pass
class C(B): pass
class D(object): pass
class E(B, D): pass
print C.__mro__
print E.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
(<class '__main__.E'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.D'>, <type 'object'>)

下面的代码简单的画出了 A,B,C,D,E 五个对象的层级关系,但是很不完善,没有递归查询

In [3]:
def render_mro(*classes):
    dot = Digraph()

    for cls in classes:
        for c in cls.__mro__[1:]:
            if c is object:
                continue
            dot.node(cls.__name__, cls.__name__)
            dot.node(c.__name__, c.__name__)
            dot.edge(c.__name__, cls.__name__)

    return dot

render_mro(A, B, C, D, E)
Out[3]:
%3 B B C C B->C E E B->E A A A->B A->C A->E D D D->E

现在封装成一个类,并加上递归查找

In [4]:
class MROGraph(object):
    
    def __init__(self, *classes, **kwargs):
        self.classes = classes
        self.dot = Digraph(**kwargs)
        self._classes = set()
        self._edges = set()
        self._nodes = set()

    def add_node(self, id_, label):
        if (id_, label) in self._nodes:
            return
        self.dot.node(id_, label)
        self._nodes.add((id_, label))

    def add_edge(self, left, right):
        if (left, right) in self._edges:
            return
        self.dot.edge(left, right)
        self._edges.add((left, right))

    def render_cls(self, cls):
        if cls.__name__ in self._classes:
            return
        self.add_node(cls.__name__, cls.__name__)
        for c in cls.__mro__[1:]:
            if c is object:
                continue
            self.add_node(c.__name__, c.__name__)
            self.add_edge(c.__name__, cls.__name__)
            self.render_cls(c)
        self._classes.add(cls.__name__)

    def render(self):
        for cls in self.classes:
            self.render_cls(cls)
        return self.dot


g = MROGraph(C, E)
g.render()
Out[4]:
%3 C C B B B->C E E B->E A A A->C A->B A->E D D D->E

可视化 Django 的 Class Based View

比如以 FormView 为例

In [5]:
from django.views.generic import View, TemplateView, DetailView, ListView, FormView
g = MROGraph(FormView, graph_attr={'size': '10,5', 'ratio': 'fill'})
g.render()
Out[5]:
%3 FormView FormView TemplateResponseMixin TemplateResponseMixin TemplateResponseMixin->FormView BaseFormView BaseFormView BaseFormView->FormView FormMixin FormMixin FormMixin->FormView FormMixin->BaseFormView ContextMixin ContextMixin ContextMixin->FormView ContextMixin->BaseFormView ContextMixin->FormMixin ProcessFormView ProcessFormView ProcessFormView->FormView ProcessFormView->BaseFormView View View View->FormView View->BaseFormView View->ProcessFormView
In [6]:
g = MROGraph(DetailView, graph_attr={'size': '10,5', 'ratio': 'fill'})
g.render()
Out[6]:
%3 DetailView DetailView SingleObjectTemplateResponseMixin SingleObjectTemplateResponseMixin SingleObjectTemplateResponseMixin->DetailView TemplateResponseMixin TemplateResponseMixin TemplateResponseMixin->DetailView TemplateResponseMixin->SingleObjectTemplateResponseMixin BaseDetailView BaseDetailView BaseDetailView->DetailView SingleObjectMixin SingleObjectMixin SingleObjectMixin->DetailView SingleObjectMixin->BaseDetailView ContextMixin ContextMixin ContextMixin->DetailView ContextMixin->BaseDetailView ContextMixin->SingleObjectMixin View View View->DetailView View->BaseDetailView
In [7]:
from django.views.generic import View, TemplateView, DetailView, ListView, FormView
g = MROGraph(View, TemplateView, DetailView, ListView, FormView, graph_attr={'size': '10,5', 'ratio': 'fill'})
g.render()
Out[7]:
%3 View View TemplateView TemplateView View->TemplateView DetailView DetailView View->DetailView BaseDetailView BaseDetailView View->BaseDetailView ListView ListView View->ListView BaseListView BaseListView View->BaseListView FormView FormView View->FormView BaseFormView BaseFormView View->BaseFormView ProcessFormView ProcessFormView View->ProcessFormView TemplateResponseMixin TemplateResponseMixin TemplateResponseMixin->TemplateView TemplateResponseMixin->DetailView SingleObjectTemplateResponseMixin SingleObjectTemplateResponseMixin TemplateResponseMixin->SingleObjectTemplateResponseMixin TemplateResponseMixin->ListView MultipleObjectTemplateResponseMixin MultipleObjectTemplateResponseMixin TemplateResponseMixin->MultipleObjectTemplateResponseMixin TemplateResponseMixin->FormView ContextMixin ContextMixin ContextMixin->TemplateView ContextMixin->DetailView ContextMixin->BaseDetailView SingleObjectMixin SingleObjectMixin ContextMixin->SingleObjectMixin ContextMixin->ListView ContextMixin->BaseListView MultipleObjectMixin MultipleObjectMixin ContextMixin->MultipleObjectMixin ContextMixin->FormView ContextMixin->BaseFormView FormMixin FormMixin ContextMixin->FormMixin SingleObjectTemplateResponseMixin->DetailView BaseDetailView->DetailView SingleObjectMixin->DetailView SingleObjectMixin->BaseDetailView MultipleObjectTemplateResponseMixin->ListView BaseListView->ListView MultipleObjectMixin->ListView MultipleObjectMixin->BaseListView BaseFormView->FormView FormMixin->FormView FormMixin->BaseFormView ProcessFormView->FormView ProcessFormView->BaseFormView

有待完善的问题

  • 显示 Class 的类签名,比如标签显示为 E(B, D)
  • meta class 的支持
  • 上述代码通过 cls.__name__ 确定类的唯一性,这样合理吗?
  • old style class 的支持