可视化 python 中的嵌套函数调用
Visualizing nested function calls in python
我需要一种方法来可视化 python 中的嵌套函数调用,最好是在树状结构中。所以,如果我有一个包含 f(g(x,h(y)))
的字符串,我想创建一个树,使关卡更易读。例如:
f()
|
g()
/ \
x h()
|
y
或者,当然,更好的是,像 sklearn.tree.plot_tree
创建的那样的树图。
这似乎是一个很久以前就有人解决过的问题,但到目前为止我一直在努力寻找它。仅供参考,这是为了可视化遗传编程输出,它往往具有像这样非常复杂的字符串。
谢谢!
更新:
toytree 和 toyplot 非常接近,但还差得远:
这是生成的:
import toytree, toyplot
mystyle = {"layout": 'down','node_labels':True}
s = '((x,(y)));'
toytree.tree(s).draw(**mystyle);
很接近,但节点标签不是字符串...
更新二:
我发现了另一种可能的解决方案,使我更接近文本形式:
https://rosettacode.org/wiki/Visualize_a_tree#Python
tree2 = Node('f')([
Node('g')([
Node('x')([]),
Node('h')([
Node('y')([])
])
])
])
print('\n\n'.join([drawTree2(True)(False)(tree2)]))
结果如下:
没错,但我必须手动将我的字符串转换为 drawTree2
函数需要的节点表示法。
这是一个使用 pyparsing 和 asciitree 的解决方案。这可以适用于解析几乎所有内容并生成绘图所需的任何数据结构。在这种情况下,代码会生成适合输入到 asciitree 的嵌套字典。
#!/usr/bin/env python3
from collections import OrderedDict
from asciitree import LeftAligned
from pyparsing import Suppress, Word, alphas, Forward, delimitedList, ParseException, Optional
def grammar():
lpar = Suppress('(')
rpar = Suppress(')')
identifier = Word(alphas).setParseAction(lambda t: (t[0], {}))
function_name = Word(alphas)
expr = Forward()
function_arg = delimitedList(expr)
function = (function_name + lpar + Optional(function_arg) + rpar).setParseAction(lambda t: (t[0] + '()', OrderedDict(t[1:])))
expr << (function | identifier)
return function
def parse(expr):
g = grammar()
try:
parsed = g.parseString(expr, parseAll=True)
except ParseException as e:
print()
print(expr)
print(' ' * e.loc + '^')
print(e.msg)
raise
return dict([parsed[0]])
if __name__ == '__main__':
expr = 'f(g(x,h(y)))'
tree = parse(expr)
print(LeftAligned()(tree))
输出:
f()
+-- g()
+-- x
+-- h()
+-- y
编辑
通过一些调整,您可以构建适合在您最喜欢的图形库中绘制的边列表(下面的 igraph 示例)。
#!/usr/bin/env python3
import igraph
from pyparsing import Suppress, Word, alphas, Forward, delimitedList, ParseException, Optional
class GraphBuilder(object):
def __init__(self):
self.labels = {}
self.edges = []
def add_edges(self, source, targets):
for target in targets:
self.add_edge(source, target)
return source
def add_edge(self, source, target):
x = self.labels.setdefault(source, len(self.labels))
y = self.labels.setdefault(target, len(self.labels))
self.edges.append((x, y))
def build(self):
g = igraph.Graph()
g.add_vertices(len(self.labels))
g.vs['label'] = sorted(self.labels.keys(), key=lambda l: self.labels[l])
g.add_edges(self.edges)
return g
def grammar(gb):
lpar = Suppress('(')
rpar = Suppress(')')
identifier = Word(alphas)
function_name = Word(alphas).setParseAction(lambda t: t[0] + '()')
expr = Forward()
function_arg = delimitedList(expr)
function = (function_name + lpar + Optional(function_arg) + rpar).setParseAction(lambda t: gb.add_edges(t[0], t[1:]))
expr << (function | identifier)
return function
def parse(expr, gb):
g = grammar(gb)
g.parseString(expr, parseAll=True)
if __name__ == '__main__':
expr = 'f(g(x,h(y)))'
gb = GraphBuilder()
parse(expr, gb)
g = gb.build()
layout = g.layout('tree', root=len(gb.labels)-1)
igraph.plot(g, layout=layout, vertex_size=30, vertex_color='white')
我需要一种方法来可视化 python 中的嵌套函数调用,最好是在树状结构中。所以,如果我有一个包含 f(g(x,h(y)))
的字符串,我想创建一个树,使关卡更易读。例如:
f()
|
g()
/ \
x h()
|
y
或者,当然,更好的是,像 sklearn.tree.plot_tree
创建的那样的树图。
这似乎是一个很久以前就有人解决过的问题,但到目前为止我一直在努力寻找它。仅供参考,这是为了可视化遗传编程输出,它往往具有像这样非常复杂的字符串。
谢谢!
更新:
toytree 和 toyplot 非常接近,但还差得远:
这是生成的:
import toytree, toyplot
mystyle = {"layout": 'down','node_labels':True}
s = '((x,(y)));'
toytree.tree(s).draw(**mystyle);
很接近,但节点标签不是字符串...
更新二: 我发现了另一种可能的解决方案,使我更接近文本形式: https://rosettacode.org/wiki/Visualize_a_tree#Python
tree2 = Node('f')([
Node('g')([
Node('x')([]),
Node('h')([
Node('y')([])
])
])
])
print('\n\n'.join([drawTree2(True)(False)(tree2)]))
结果如下:
没错,但我必须手动将我的字符串转换为 drawTree2
函数需要的节点表示法。
这是一个使用 pyparsing 和 asciitree 的解决方案。这可以适用于解析几乎所有内容并生成绘图所需的任何数据结构。在这种情况下,代码会生成适合输入到 asciitree 的嵌套字典。
#!/usr/bin/env python3
from collections import OrderedDict
from asciitree import LeftAligned
from pyparsing import Suppress, Word, alphas, Forward, delimitedList, ParseException, Optional
def grammar():
lpar = Suppress('(')
rpar = Suppress(')')
identifier = Word(alphas).setParseAction(lambda t: (t[0], {}))
function_name = Word(alphas)
expr = Forward()
function_arg = delimitedList(expr)
function = (function_name + lpar + Optional(function_arg) + rpar).setParseAction(lambda t: (t[0] + '()', OrderedDict(t[1:])))
expr << (function | identifier)
return function
def parse(expr):
g = grammar()
try:
parsed = g.parseString(expr, parseAll=True)
except ParseException as e:
print()
print(expr)
print(' ' * e.loc + '^')
print(e.msg)
raise
return dict([parsed[0]])
if __name__ == '__main__':
expr = 'f(g(x,h(y)))'
tree = parse(expr)
print(LeftAligned()(tree))
输出:
f()
+-- g()
+-- x
+-- h()
+-- y
编辑
通过一些调整,您可以构建适合在您最喜欢的图形库中绘制的边列表(下面的 igraph 示例)。
#!/usr/bin/env python3
import igraph
from pyparsing import Suppress, Word, alphas, Forward, delimitedList, ParseException, Optional
class GraphBuilder(object):
def __init__(self):
self.labels = {}
self.edges = []
def add_edges(self, source, targets):
for target in targets:
self.add_edge(source, target)
return source
def add_edge(self, source, target):
x = self.labels.setdefault(source, len(self.labels))
y = self.labels.setdefault(target, len(self.labels))
self.edges.append((x, y))
def build(self):
g = igraph.Graph()
g.add_vertices(len(self.labels))
g.vs['label'] = sorted(self.labels.keys(), key=lambda l: self.labels[l])
g.add_edges(self.edges)
return g
def grammar(gb):
lpar = Suppress('(')
rpar = Suppress(')')
identifier = Word(alphas)
function_name = Word(alphas).setParseAction(lambda t: t[0] + '()')
expr = Forward()
function_arg = delimitedList(expr)
function = (function_name + lpar + Optional(function_arg) + rpar).setParseAction(lambda t: gb.add_edges(t[0], t[1:]))
expr << (function | identifier)
return function
def parse(expr, gb):
g = grammar(gb)
g.parseString(expr, parseAll=True)
if __name__ == '__main__':
expr = 'f(g(x,h(y)))'
gb = GraphBuilder()
parse(expr, gb)
g = gb.build()
layout = g.layout('tree', root=len(gb.labels)-1)
igraph.plot(g, layout=layout, vertex_size=30, vertex_color='white')