在这篇文章中,我们将为您详细介绍是什么导致NetworkX和PyGraphViz单独运行但不能一起正常运行?的内容,并且讨论关于networkx.graph的相关问题。此外,我们还会涉及一些关于com
在这篇文章中,我们将为您详细介绍是什么导致NetworkX和PyGraphViz单独运行但不能一起正常运行?的内容,并且讨论关于networkx.graph的相关问题。此外,我们还会涉及一些关于com.google.inject.grapher.graphviz.GraphvizGrapher的实例源码、com.google.inject.grapher.graphviz.GraphvizModule的实例源码、Graphviz学习(二)——有关Graphviz的概述、Graphviz是什么的知识,以帮助您更全面地了解这个主题。
本文目录一览:- 是什么导致NetworkX和PyGraphViz单独运行但不能一起正常运行?(networkx.graph)
- com.google.inject.grapher.graphviz.GraphvizGrapher的实例源码
- com.google.inject.grapher.graphviz.GraphvizModule的实例源码
- Graphviz学习(二)——有关Graphviz的概述
- Graphviz是什么
是什么导致NetworkX和PyGraphViz单独运行但不能一起正常运行?(networkx.graph)
我正在努力学习一些Python图形可视化。我发现一些博客文章做了一些
我想尝试的事情。不幸的是,我并没有走太远,遇到了这个错误:AttributeError: 'module'
object has no attribute 'graphviz_layout'
在我的系统上 重现错误 的最简单的代码片段是这样,
In [1]: import networkx as nx
In [2]: G=nx.complete_graph(5)
In [3]: nx.draw_graphviz(G)
------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-3-481ad1c1771c> in <module>()
----> 1 nx.draw_graphviz(G)
/usr/lib/python2.7/site-packages/networkx/drawing/nx_pylab.pyc in draw_graphviz(G,prog,**kwargs)
982 See networkx.draw_networkx() for a description of optional keywords.
983 """
--> 984 pos = nx.drawing.graphviz_layout(G,prog)
985 draw(G,pos,**kwargs)
986
AttributeError: 'module' object has no attribute 'graphviz_layout'
我发现了类似的问题,并且帖子对此组合有困难,但错误并不完全相同。一个很近,但是它自动解决了。
首先,我核实了所有所需的软件包
为NetworkX和PyGraphViz(其中列出了类似的要求SciPy的进行安装)。
接下来,我寻找了一些片段来测试我在Python中安装这些模块的情况。
前两个示例来自NetworkX参考文档。这列出了同时使用MatPlotLib和GraphViz的一些示例片段。
MatPlotLib代码示例对我有用(将图像渲染到屏幕上) ,
In [11]: import networkx as nx
In [12]: G=nx.complete_graph(5)
In [13]: import matplotlib.pyplot as plt
In [13]: nx.draw(G)
In [13]: plt.show()
但是, GraphViz片段也会产生类似的错误,
In [16]: import networkx as nx
In [17]: G=nx.complete_graph(5)
In [18]: H=nx.from_agraph(A)
------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-18-808fa68cefaa> in <module>()
----> 1 H=nx.from_agraph(A)
AttributeError: 'module' object has no attribute 'from_agraph'
In [19]: A=nx.to_agraph(G)
------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-19-32d1616bb41a> in <module>()
----> 1 A=nx.to_agraph(G)
AttributeError: 'module' object has no attribute 'to_agraph'
In [20]: print G
complete_graph(5)
然后,我尝试
了Layout&Drawing上 PyGraphViz的教程页面 。这也有一些剪。 PyGraphViz
与Neato(默认),PyDot和Circo Post Script输出(使用Gimp查看) 一起传递
。(唯一的区别是这些PyGraphViz示例未呈现到显示器,而是呈现到文件)。
In [1]: import pygraphviz as pgv
In [2]: d={'1': {'2': None},'2': {'1': None,'3': None},'3': {'2': None}}
In [3]: A=pgv.AGraph(d)
In [4]: A.write("pygraphviz_test_01.dot")
In [5]: A.layout()
In [6]: A.draw('pygraphviz_test_01.png')
*PyGraphViz *增加了复杂性,
因此需要GraphViz软件包二进制文件才能正常工作。我正在使用Arch
Linux,并安装了该发行版的版本。Arch
Linux有一个示例来测试安装(再次,输出到文件)
也通过了 。
我想念什么? 是什么导致NetworkX和PyGraphViz单独运行但不能一起正常运行?
com.google.inject.grapher.graphviz.GraphvizGrapher的实例源码
@Test public void dumpGraph() throws Exception { List<Module> modules = new ArrayList<>(); modules.add(new GraphvizModule()); modules.add(new RuneLiteModule()); runelite.setClient(client); PluginManager pluginManager = new PluginManager(); pluginManager.loadCorePlugins(); for (Plugin p : pluginManager.getPlugins()) { modules.add(p); } File file = folder.newFile(); try (PrintWriter out = new PrintWriter(file,"UTF-8")) { Injector injector = Guice.createInjector(modules); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(injector); } }
private static void maybeGraphGuiceDependencies(final Injector injector,final Class<? extends TextGroupEntryPoint> entryPointClass,final Parameters params) throws IOException { final Optional<File> dotFile = params.getoptionalCreatableFile(GRAPH_DEPENDENCIES_ParaM); if (dotFile.isPresent()) { final Injector grapherInjector = Guice.createInjector(new GraphvizModule()); final GraphvizGrapher grapher = grapherInjector.getInstance(GraphvizGrapher.class); // orient graph vertically grapher.setRankdir("TB"); log.info("Writing Guice configuration graph to {}. To compile it,do dot -T png {}",dotFile.get(),dotFile.get()); try (PrintWriter out = new PrintWriter(Files.asCharSink(dotFile.get(),Charsets.UTF_8) .openBufferedStream())) { grapher.setout(out); grapher.graph(injector,ImmutableSet.<Key<?>>of(Key.get(entryPointClass))); } } }
public static void main(String[] args) throws Exception { // Todo(phopkins): Switch to Stage.TOOL when issue 297 is fixed. Injector demoInjector = Guice.createInjector( Stage.DEVELOPMENT,new BackToTheFutureModule(),new MultibinderModule(),new PrivateTestModule()); PrintWriter out = new PrintWriter(new File(args[0]),"UTF-8"); Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
public void writeGraph(File file,Injector injector) throws IOException { final PrintWriter out = new PrintWriter(file,Charsets.UTF_8.name()); final GraphvizGrapher grapher = Guice.createInjector(new GraphvizModule()).getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(injector); }
private static void writeDotFile(String filename,Injector demoInjector) throws IOException { PrintWriter out = new PrintWriter(new File(filename),"UTF-8"); com.google.inject.Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
public void graph(String filename,Charsets.UTF_8.name()); Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
public void graph(String filename,Charsets.UTF_8_NAME); Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
public static void main(String[] args) throws Exception { // Todo(phopkins): Switch to Stage.TOOL when issue 297 is fixed. Injector demoInjector = Guice.createInjector(Stage.DEVELOPMENT,"UTF-8"); Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
public static void main(String[] args) throws Exception { // Todo(phopkins): Switch to Stage.TOOL when issue 297 is fixed. Injector demoInjector = Guice.createInjector(Stage.DEVELOPMENT,"UTF-8"); Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
com.google.inject.grapher.graphviz.GraphvizModule的实例源码
@Test public void dumpGraph() throws Exception { List<Module> modules = new ArrayList<>(); modules.add(new GraphvizModule()); modules.add(new RuneLiteModule()); runelite.setClient(client); PluginManager pluginManager = new PluginManager(); pluginManager.loadCorePlugins(); for (Plugin p : pluginManager.getPlugins()) { modules.add(p); } File file = folder.newFile(); try (PrintWriter out = new PrintWriter(file,"UTF-8")) { Injector injector = Guice.createInjector(modules); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(injector); } }
private static void maybeGraphGuiceDependencies(final Injector injector,final Class<? extends TextGroupEntryPoint> entryPointClass,final Parameters params) throws IOException { final Optional<File> dotFile = params.getoptionalCreatableFile(GRAPH_DEPENDENCIES_ParaM); if (dotFile.isPresent()) { final Injector grapherInjector = Guice.createInjector(new GraphvizModule()); final GraphvizGrapher grapher = grapherInjector.getInstance(GraphvizGrapher.class); // orient graph vertically grapher.setRankdir("TB"); log.info("Writing Guice configuration graph to {}. To compile it,do dot -T png {}",dotFile.get(),dotFile.get()); try (PrintWriter out = new PrintWriter(Files.asCharSink(dotFile.get(),Charsets.UTF_8) .openBufferedStream())) { grapher.setout(out); grapher.graph(injector,ImmutableSet.<Key<?>>of(Key.get(entryPointClass))); } } }
private static void graph(String filename,Injector gameInjector) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (PrintWriter bout = new PrintWriter(baos)) { Injector injector = Guice.createInjector(new GrapherModule(),new GraphvizModule()); GraphvizRenderer renderer = injector.getInstance(GraphvizRenderer.class); renderer.setout(bout); injector.getInstance(Injectorgrapher.class) .of(gameInjector) .graph(); } try (PrintWriter out = new PrintWriter( new File(filename),"UTF-8")) { String s = baos.toString("UTF-8"); s = fixGrapherBug(s); s = hideClasspaths(s); out.write(s); } }
public static void main(String[] args) throws Exception { // Todo(phopkins): Switch to Stage.TOOL when issue 297 is fixed. Injector demoInjector = Guice.createInjector( Stage.DEVELOPMENT,new BackToTheFutureModule(),new MultibinderModule(),new PrivateTestModule()); PrintWriter out = new PrintWriter(new File(args[0]),"UTF-8"); Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
public void writeGraph(File file,Injector injector) throws IOException { final PrintWriter out = new PrintWriter(file,Charsets.UTF_8.name()); final GraphvizGrapher grapher = Guice.createInjector(new GraphvizModule()).getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(injector); }
private static void writeDotFile(String filename,Injector demoInjector) throws IOException { PrintWriter out = new PrintWriter(new File(filename),"UTF-8"); com.google.inject.Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
private static void graph(String filename,"UTF-8"); Injector injector = Guice.createInjector(new GrapherModule(),new GraphvizModule()); GraphvizRenderer renderer = injector.getInstance(GraphvizRenderer.class); renderer.setout(out).setRankdir("TB"); injector.getInstance(Injectorgrapher.class).of(demoInjector).graph(); }
public void graph(String filename,Charsets.UTF_8.name()); Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
public void graph(String filename,Charsets.UTF_8_NAME); Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
public static void main(String[] args) throws Exception { // Todo(phopkins): Switch to Stage.TOOL when issue 297 is fixed. Injector demoInjector = Guice.createInjector(Stage.DEVELOPMENT,"UTF-8"); Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
public static void main(String[] args) throws Exception { // Todo(phopkins): Switch to Stage.TOOL when issue 297 is fixed. Injector demoInjector = Guice.createInjector(Stage.DEVELOPMENT,"UTF-8"); Injector injector = Guice.createInjector(new GraphvizModule()); GraphvizGrapher grapher = injector.getInstance(GraphvizGrapher.class); grapher.setout(out); grapher.setRankdir("TB"); grapher.graph(demoInjector); }
Graphviz学习(二)——有关Graphviz的概述
(一)、总的架构
今天我们讲一下Graphviz的概述,从前一章我们明白,Graphviz不只是一个解释器那么简单,它还包含着包括C接口在内的诸多接口,事实上,Graphviz可以根据用户的需求,在运行的各个环节进行定制和嵌入,下面这张概念图,是我对Graphviz机制的了解:
在这里,我们会一一解释,并且讨论几个关键的地方:C接口、布局计算与渲染。
首先看一看输入,Graphviz的输入包括三个:脚本输入(DOT)、编程语言输入(C语言、Python语言甚至是Java语言、PHP)、图形界面输入。事实上,无论是那一种输入,到最后都会调用C接口输入。
然后就是C语言接口,也就是我们上一章所说的C语言接口的例子,着一个环节主要将输入变成Graphviz可以识别的内部格式,也就是用于描述图的数据结构,包括Agraph_t、Agnode_t、Agedge等等。
产生内部格式后,程序应该交给Graphviz预定义的布局引擎进行计算,这个过程将会计算出图的各个元素的集合信息,例如节点的位置、大小、形状;边的形状(Graphviz使用贝塞尔曲线显示边)等等。同时,用户还可以自己定制自己的布局引擎。
下一步就是将内部格式转换为可以被用户所了解的格式,也就是渲染环节,这一环节使用的机制称为渲染引擎,经过渲染,可以将信息转换为DOT格式(就是脚本,可以用于存储、调试)、图片(有很多、我就不说了)。用户也可以定制自己的渲染引擎,以生成其它格式,或者向用户程序生成图像。
在Graphviz的C接口,也就是GVC中,封装了其中的三个模块:数据结构以及操作、布局引擎、渲染引擎。每一个模块都可以单独使用,使之嵌入到应用程序之中,并且布局引擎以及渲染引擎可以由用户定制,执行用户自己指定的行为。例如,我们将使用布局引擎嵌入至应用程序之中,应用程序可以使用布局引擎的计算结果,去指导应用程序的绘图;可以定制自己的渲染模块,使输出格式更为多样。
(二)、关于数据结构
1. 在gvc(Graphviz的C语言封装)中、最重要的数据结构莫过于这三个:Agraph_t、Agnode_t、Agedge_t。这三个数据分别代表图(子图)、节点、边。gvc中所有操作可以说是围绕这三个元素进行的,也可以分为两类:结构操作、属性操作。
通过结构操作,用户可以完成对图的结构的构建,如agedge(graph,node1,node2,name,flag),会生成一个图graph中的边,边的两端为node1与node2,标识(不是label)为name。这些操作都可以导致图结构的变动。
通过属性操作,用户可以完成对图中元素属性的改动,Graphviz提供了大量的属性,用户甚至可以定义自己的属性,因此具有良好的自由度和重用性。值得一提的是,Graphviz的属性只接受字符串形式,也就是说,我们提取和指定属性时,即使属性是数值型或其他不是字符串的类型,也要转换为字符串,Graphviz会通过内部的转换机制,将字符串转换为相应的类型;同时,属性必需先初始化,再提取和指定,没有初始化的情况下,Graphviz会使用内部预定义的默认值,比如字体的颜色属性fontcolor默认是黑色、字体名称fontname默认时Times-Roman、字体大小fontsize默认时14。
2.经过布局计算,计算结果将会储存至数据结构自身的其他字段当中之中,由于Graphviz向用户隐藏了数据结构的具体细节,用户只能通过特定的接口,而不是直接通过结构的定义,去访问布局计算的结果;访问计算结果的接口定义在头文件include/graphviz/types.h中,形式为ElemType_ResultField(ElemHandle),ElemType_为元素的类型前缀,图的前缀为GD_,节点的前缀为ND_,边的前缀为ED_;如果我要访问某一个节点的形状,可以使用宏ND_shape(instance)->name,类型为char*。
3.关于内存问题,以下几个部分的内存,是需要程序员自己管理的,以防止内存溢出:一是GVC的句柄GVC_t,再GVC使用之前要使用gvContext()产生,再使用完毕后要使用gvFreeContext(gv)进行回收;二是,数据结构,例如图Agraph_t,使用完毕后也要使用agclose(g),来回收,只要不是顶层的元素,例如子图、节点、边、属性句柄,他们在创建的时候就已经被顶层数据结构所标记,因此回收时不需要显式的回收,因为顶层回收时会自动回收,但是,如果中途就不使用这一个元素了,就要显式的回收,例如Agnode_t使用agdelnode回收;三是,布局结果需要显式回收,一般使用gvFreeLayout(gv,g)进行回收。
(三)、关于脚本语言
再Graphviz之中,使用的最多的莫过于脚本语言,相比C接口,脚本语言跟加便捷易懂,也不需要使用太多的精力去学习。一般说,C接口主要用于对Graphviz进行扩展以及嵌入到一些应用程序之中。
可以使用dot程序生成带有汉字信息的图片,需要注意两点,否则生成的图片中汉字会被显示为乱码:一是文件应该使用UTF-8编码保存(再Windows的要注意了,不是默认的ANSI编码);二是、字体应该指定为支持汉字的字体,如宋体SimSun,一般最好使用一些大多数平台共用的字体例如Times字体。
Graphviz是什么
graphviz诞生于at&t的bell实验室的一个开源的 (epl授权)、跨平台的脚本自动化绘图软件工具。graphviz使用一种称为dot语言的dsl语言编写script file脚本文件,然后使用layout布局引擎解析这个script file脚本文件完成自动化布局渲染 。
本教程操作环境:windows7系统、Dell G3电脑。
Graphviz是什么?
Graphviz(Graph Visualization)是1990年代初诞生于AT&T的Bell实验室的一个开源的 (EPL授权) 、跨平台 的 脚本自动化绘图软件工具。
Graphviz使用一种称为dot语言的DSL(Domain Special Language)语言编写Script File脚本文件,然后使用Layout布局引擎解析这个Script File脚本文件完成 自动化布局渲染 。
AT&T是美国三大的电信运营商之一,现在美国的电信运营商行业也是从原AT&T被强行支解后再不断合并后形成群雄逐鹿的竞争局面。
AT&T的前身是Bell电话公司,Bell电话公司是发明电话的公司。
在IT软件世界中,Bell实验室创造了Unix、C语言等一系列伟大的发明。
Graphviz脚本文件一般以.gv或.dot为文件扩展名,由于Microsoft Office Word模板文件的扩展名也是.dot,为避免冲突,一般建议Graphviz脚本文件的扩展名保存为.gv。
备注: 在Graphviz中,dot这个词主要有三种不同的用途:
Graphviz的Script脚本的语言名称/语法名称
Graphviz的 其中一种 Layout名称,同时也是 其中一种 CLI命令名称
Graphviz的 其中一种 Script File(脚本文件)常用扩展名
为什么要使用Graphviz?
自动排版效率更高: Graphviz主要用于绘制“关系图”,Graphviz自动排版以使“最小化连线交叉”,Graphviz的自动排版比“所见即所得”的绘画软件(如:Omnigraffle、Microsoft Office Visio等)
文本文件管理更方便: VCS(Version Control System版本控制系统)对“所见即所得”的绘画软件生成的文件无法有效地进行版本管理,而文本文件则可以在VCS中有效地被进行版本管理起来
其他自动化绘图工具的基础: Graphviz是其他自动化绘图工具的基础(如PlantUML等),也是很多Data Visualization数据可视化工具的基础,有点类似于Python中Matplotlib的作用
Graphviz的基本组成结构和使用流程
Graphviz的基本组成结构包括Layout自动化布局工具和Script脚本文件两部分。
Script脚本文件主要包括Elements实体和Attributes属性两部分。
Elements实体主要包括Graph图、Node节点、Edge连线三种类型。
备注: 如果需要在软件中调用Graphviz, Graphviz还提供了C/CPP、Java、Python、php等语言的API。
Graphviz基本组成结构
Graphviz使用起来也非常简单方便,其一般流程如下:
Graphviz的一般使用流程
Graphviz的MacOS独立客户端
在MacOS 10.6之前由Glen Low开发的MacOS X GUI版本于2004年获得了两项苹果设计奖( "Best Product New to Mac OS X Runner-Up" 和 "Best OS X Open Source Product 2004 Winner" ),当时的MacOS X基于PowerPC CPU,该Graphviz GUI版自2004年08月23日发布支持MacOS 10.3的1.13版本之后该就没有再更新,即:该Graphviz GUI版不能在当今基于Intel CPU的macOS中运行。。
MacOS 10.2和10.3基于PowerPC CPU,2005年发布的10.4开始同时基于Intel CPU和PowerPC CPU,2009年发布的10.6不再支持PowerPC CPU。
目前暂未发现有支持MacOS X 10.6以后的独立GUI客户端,MacOS X 10.6以后可以使用CLI或在其他内嵌Graphviz工具中使用(比如:在ATOM编辑器中使用markdown-preview-enhanced插件中可嵌入Graphviz Script File)。
本文图示的脚本源码
Grpahviz基本组成结构的dot脚本源码
digraph gv_basic_structure{ label=<<B>Graphviz基本组成结构</B>>; labelloc=t; bgcolor=transparent; node[shape=box]; //edge[style=bold]; graphviz[label="Graphviz"]; subgraph{ layout[label="Layouts"]; script[label="Script Files"]; api[label="APIs"] rank=same; } graphviz -> layout; graphviz -> script; graphviz -> api; script -> subgraph{ element[label="Elements"]; attribute[label="Attributes"]; rank=same; } layout -> subgraph{ layout_etc[label="......"]; layout_dot[label="dot"]; layout_neato[label="neato"]; } element -> subgraph{ ele_graph[label="Graph"]; ele_node[label="Node"]; ele_edge[label="Edge"]; } }
Graphviz使用一般流程的PlantUML源码
@startuml start :定义Graph属性; :定义Node、Edge默认属性; :添加Node和Edge; :定义特定Node、Edge的个性属性; :使用CLI或GUI布局引擎工具渲染绘制; end @enduml
更多编程相关知识,请访问:编程教学!!
以上就是Graphviz是什么的详细内容,更多请关注php中文网其它相关文章!
我们今天的关于是什么导致NetworkX和PyGraphViz单独运行但不能一起正常运行?和networkx.graph的分享就到这里,谢谢您的阅读,如果想了解更多关于com.google.inject.grapher.graphviz.GraphvizGrapher的实例源码、com.google.inject.grapher.graphviz.GraphvizModule的实例源码、Graphviz学习(二)——有关Graphviz的概述、Graphviz是什么的相关信息,可以在本站进行搜索。
本文标签: