3、Python图库入门:NetworkX安装与基础使用、创建图、添加节点与边

说实话,做任务依赖图可视化,选对工具就成功了一半。我这些年试过不少图库,Graphviz 太死板,PyGraphviz 又太底层。直到碰上 NetworkX,才觉得「嗯,就是它了」。它轻巧、灵活,而且跟 Python 的数据生态无缝衔接。今天我们就从零开始,把它玩熟。

3.1 安装 NetworkX

安装没什么玄学,一行命令搞定。我个人习惯在虚拟环境里装,避免污染全局。

pip install networkx

如果你用的是 Anaconda,也可以:

conda install networkx

装完之后,验证一下:

import networkx as nx
print(nx.__version__)

看到版本号就说明成了。我遇到过有人装完报错,多半是 pip 版本太旧,先升级 pip 再装就好。

小提示: 建议同时安装 matplotlib,后面画图会用到。命令:pip install matplotlib

3.2 创建图:三种基本类型

NetworkX 支持三种图结构,你得先搞清楚它们的区别。说白了,就是看边有没有方向、允不允许重复。

图类型 类名 边是否有方向 是否允许重边 适用场景
无向图 Graph 否 否 社交网络、道路网
有向图 DiGraph 是 否 任务依赖、流程控制
多重图 MultiGraph / MultiDiGraph 可选 是 多路传输、复杂关系

创建图非常简单:

import networkx as nx

# 无向图
G = nx.Graph()

# 有向图
DG = nx.DiGraph()

# 多重有向图
MDG = nx.MultiDiGraph()

你想想看,任务依赖图天然就是有向的——任务 A 必须在任务 B 之前完成。所以后面我们主要用 DiGraph。

3.3 添加节点

节点可以理解成图中的「点」。在任务依赖图里,每个节点就是一个任务。

3.3.1 添加单个节点

G.add_node('task_1')

节点名可以是字符串、数字,甚至元组。我习惯用字符串,可读性好。

3.3.2 批量添加节点

tasks = ['task_1', 'task_2', 'task_3', 'task_4']
G.add_nodes_from(tasks)

也可以从列表、集合、甚至文件里读进来。我在项目中经常从 CSV 里读任务列表,一行一个节点。

3.3.3 带属性的节点

节点可以挂属性,比如任务耗时、负责人、状态等。这在实际项目中非常有用。

G.add_node('task_1', duration=5, owner='张三', status='done')
G.add_node('task_2', duration=3, owner='李四', status='pending')

获取属性也很方便:

print(G.nodes['task_1'])  # 输出:{'duration': 5, 'owner': '张三', 'status': 'done'}
核心要点: 节点属性是后续做故障定位的关键。比如你可以根据 status 字段快速标红失败的任务。

3.4 添加边

边就是节点之间的「连线」。在任务依赖图里,边表示依赖关系——从前置任务指向后置任务。

3.4.1 添加单条边

G.add_edge('task_1', 'task_2')

这表示 task_1 完成后才能开始 task_2。

3.4.2 批量添加边

edges = [('task_1', 'task_2'), ('task_2', 'task_3'), ('task_3', 'task_4')]
G.add_edges_from(edges)

3.4.3 带属性的边

边也可以挂属性,比如依赖类型、传输数据量等。

G.add_edge('task_1', 'task_2', weight=1.0, relation='finish-to-start')
注意: 添加边时,如果节点不存在,NetworkX 会自动创建节点。这虽然方便,但容易导致节点属性丢失。我曾经因为这个踩过坑——批量加边后,发现某些节点没有初始化属性,排查了半天。建议先加节点,再加边。

3.5 图的查看与基础操作

图建好了,怎么查看?几个常用方法:

# 查看节点数
print(G.number_of_nodes())

# 查看边数
print(G.number_of_edges())

# 列出所有节点
print(list(G.nodes()))

# 列出所有边
print(list(G.edges()))

# 查看某个节点的邻居
print(list(G.neighbors('task_1')))

这些方法在调试时特别有用。我经常在加完一批边后,用 number_of_edges() 确认数量对不对。

3.6 知识体系总览

下面这张图帮你理清本章的核心脉络:

NetworkX 基础使用知识体系 安装 pip install networkx 创建图 Graph / DiGraph / MultiDiGraph 添加节点 add_node / add_nodes_from 添加边 add_edge / add_edges_from 节点属性 duration / owner / status 边属性 weight / relation 查看与基础操作 nodes() / edges() / neighbors() / number_of_nodes() 💡 核心思路 先安装 → 创建图 → 加节点(带属性)→ 加边(带属性)→ 查看验证 这是构建任务依赖图的标准流程,后续可视化与故障定位都基于此

3.7 完整示例:构建一个小型任务依赖图

把上面学的串起来,写个完整的例子。假设我们有 4 个任务,依赖关系如下:

  • task_1 完成后才能开始 task_2 和 task_3
  • task_2 和 task_3 都完成后才能开始 task_4
import networkx as nx

# 1. 创建有向图
G = nx.DiGraph()

# 2. 添加节点(带属性)
G.add_node('task_1', duration=5, status='done')
G.add_node('task_2', duration=3, status='running')
G.add_node('task_3', duration=4, status='pending')
G.add_node('task_4', duration=2, status='pending')

# 3. 添加边(表示依赖关系)
G.add_edge('task_1', 'task_2')
G.add_edge('task_1', 'task_3')
G.add_edge('task_2', 'task_4')
G.add_edge('task_3', 'task_4')

# 4. 查看图信息
print(f"节点数:{G.number_of_nodes()}")
print(f"边数:{G.number_of_edges()}")
print(f"所有节点:{list(G.nodes())}")
print(f"所有边:{list(G.edges())}")

# 5. 查看 task_1 的后继任务
print(f"task_1 的后继:{list(G.successors('task_1'))}")

# 6. 查看 task_4 的前驱任务
print(f"task_4 的前驱:{list(G.predecessors('task_4'))}")

输出结果:

节点数:4
边数:4
所有节点:['task_1', 'task_2', 'task_3', 'task_4']
所有边:[('task_1', 'task_2'), ('task_1', 'task_3'), ('task_2', 'task_4'), ('task_3', 'task_4')]
task_1 的后继:['task_2', 'task_3']
task_4 的前驱:['task_2', 'task_3']
关键点: 有向图里,successors() 返回下游任务,predecessors() 返回上游任务。这在故障定位时特别有用——一个任务挂了,你可以快速找出所有受影响的后续任务。

3.8 避坑指南

最后分享几个我踩过的坑:

  • 节点名类型混用: 别一会儿用字符串,一会儿用数字。我建议统一用字符串,避免类型转换的麻烦。
  • 忘记初始化属性: 如果先加边让 NetworkX 自动创建节点,节点属性是空的。后面取属性时会报 KeyError。
  • 有向图 vs 无向图: 任务依赖必须用 DiGraph。用 Graph 会导致依赖关系丢失方向,定位故障时根本找不到根因。
  • 边重复添加: 默认图不允许重边,重复添加不会报错但也不会增加。如果你需要多条平行边,记得用 MultiDiGraph。