第 2 章 figure 与 axes
学习目标
- 理解 Figure 与 Axes 的对象关系
- 会用
plt.subplots()创建画布与坐标系 - 区分 pyplot 状态机写法与面向对象写法
- 会用
plt.subplots(1, 2)、plt.subplots(2, 2)创建多子图并索引 - 掌握
figsize控制图形大小
2.1 Figure 与 Axes:两张对象
第 1 章的图能画出来,是因为 pyplot 替我们创建了对象。Matplotlib 的核心模型只有两类对象:
Figure(画布)是整张图的外壳:一张图有且仅有一个 Figure。它负责背景、整体大小,以及容纳所有坐标系。
Axes(坐标系)是真正画数据的区域,包含两个坐标轴、刻度、网格。一个 Figure 里可以有一个或多个 Axes。
用一句话记:Figure 是白纸,Axes 是白纸上画数据的那块区域;一张白纸可以裁成多块区域。
2.2 subplots:一手拿画布,一手拿坐标系
plt.subplots() 一次性创建 Figure 和它的 Axes,并同时返回两者:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
print(type(fig))
print(type(ax))真实输出:
<class 'matplotlib.figure.Figure'>
<class 'matplotlib.axes._axes.Axes'>fig 是 Figure 对象,ax 是 Axes 对象。subplots 的完整拼写是「sub-plot」:一张画布里的若干绘图区,每个叫一个 subplot(子图)。
2.3 两种写法:pyplot 状态机 vs 面向对象
同一张图有两种画法。pyplot 状态机写法把「当前图」「当前坐标系」记在一个全局状态里,每个 plt.xxx 都作用在「当前」对象上:
import matplotlib.pyplot as plt
import numpy as np
days = np.arange(1, 8)
high = np.array([28, 31, 33, 30, 29, 32, 34])
plt.figure(figsize=(7, 4.2))
plt.plot(days, high, marker="o", label="最高气温")
plt.title("一周气温(pyplot 写法)")
plt.xlabel("日期")
plt.ylabel("气温(°C)")
plt.legend()
plt.show()面向对象写法直接用拿到的 fig 和 ax 对象,在对象上调用方法:
import matplotlib.pyplot as plt
import numpy as np
days = np.arange(1, 8)
high = np.array([28, 31, 33, 30, 29, 32, 34])
fig, ax = plt.subplots(figsize=(7, 4.2))
ax.plot(days, high, marker="o", label="最高气温")
ax.set_title("一周气温(面向对象写法)")
ax.set_xlabel("日期")
ax.set_ylabel("气温(°C)")
ax.legend()
plt.show()两段代码输出同一张图,区别只在接口:pyplot 用 plt.plot、plt.title;面向对象用 ax.plot、ax.set_title。图 2-1 是面向对象写法的真实输出:

本书从本章起统一用面向对象写法。原因:一旦一个 Figure 里有多个 Axes,pyplot 的「当前坐标系」概念会让人混淆,而面向对象写法每个对象清清楚楚。pyplot 写法见 2.5 的对比图 2-2:

2.4 figure 的参数:大小与分辨率
plt.subplots(figsize=(宽, 高)) 的 figsize 控制 Figure 的物理大小,单位是英寸。横轴宽、纵轴高的图适合趋势;近正方形适合分布。例:
fig, ax = plt.subplots(figsize=(7, 4.2)) # 宽 7 英寸,高 4.2 英寸dpi(dots per inch,每英寸点数)控制清晰度,通常 100 左右即可;要放大打印时提高 dpi。图的大小直接决定图形在报告里的观感,第 9 章讲布局时会进一步用到。
2.5 一个 Figure 里多个 Axes
plt.subplots(行数, 列数) 把画布切成网格,返回的 axes 是一个数组。一行两列:
import matplotlib.pyplot as plt
import numpy as np
days = np.arange(1, 8)
high = np.array([28, 31, 33, 30, 29, 32, 34])
low = np.array([19, 21, 22, 20, 19, 21, 23])
fig, axes = plt.subplots(1, 2, figsize=(10, 4.2))
axes[0].plot(days, high, marker="o")
axes[0].set_title("最高气温")
axes[0].set_xlabel("日期")
axes[0].set_ylabel("气温(°C)")
axes[1].plot(days, low, marker="s")
axes[1].set_title("最低气温")
axes[1].set_xlabel("日期")
axes[1].set_ylabel("气温(°C)")
plt.show()图 2-3 真实输出:

axes[0] 是左图、axes[1] 是右图。两行两列时,axes 是二维数组,用 axes[行, 列] 索引:
fig, axes = plt.subplots(2, 2, figsize=(9, 7))
axes[0, 0].plot(...) # 左上
axes[0, 1].plot(...) # 右上
axes[1, 0].plot(...) # 左下
axes[1, 1].plot(...) # 右下axes.flat 可以把二维数组拍平,方便循环设置共同属性。图 2-4 是 2×2 网格的真实输出:

2.6 读懂报错:找错对象
面向对象写法最常见的报错是在错误的对象上调用了方法。
错误一:把 subplots 返回的 axes 数组当成单个 Axes 用:
>>> fig, axes = plt.subplots(1, 2)
>>> axes.plot([1, 2, 3])
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
axes.plot([1, 2, 3])
AttributeError: 'numpy.ndarray' object has no attribute 'plot'解读:subplots(1, 2) 返回的是由两个 Axes 组成的数组(numpy.ndarray),数组本身没有 plot 方法。要画数据,得用 axes[0].plot(...)、axes[1].plot(...)。
错误二:在 Figure 上调用绘图方法:
>>> fig = plt.figure()
>>> fig.plot([1, 2, 3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
fig.plot([1, 2, 3])
AttributeError: 'Figure' object has no attribute 'plot'解读:plot 是 Axes 的方法,不是 Figure 的方法。Figure 只负责当容器。想画图:要么用 plt.plot(pyplot 自动取当前 Axes),要么用 fig.add_subplot() 先创建 Axes 再画。
动手实践
- 运行
fig, ax = plt.subplots(),分别打印type(fig)和type(ax),确认对象类型。 - 用面向对象写法画出第 1 章的一周气温图(复现图 2-1)。
- 用
plt.subplots(1, 2)画两张并排的图:左边最高气温、右边最低气温,复现图 2-3。 - 把
plt.subplots(1, 2)改成plt.subplots(2, 2)并各画一张图,复现图 2-4。 - 故意执行
axes.plot([1, 2, 3])(其中fig, axes = plt.subplots(1, 2)),阅读报错,说明问题出在哪。
常见错误
| 错误写法 | 现象 | 原因 |
|---|---|---|
axes.plot(...),其中 axes 来自 subplots(1, 2) | AttributeError: 'numpy.ndarray' object has no attribute 'plot' | subplots 返回的是 Axes 数组,要用 axes[0]、axes[1] 索引 |
fig.plot(...) | AttributeError: 'Figure' object has no attribute 'plot' | plot 是 Axes 的方法,不是 Figure 的方法 |
忘了 fig, ax = plt.subplots() 直接用 ax | NameError: name 'ax' is not defined | 先创建再使用 |
混用 plt.title 与 ax.plot 后布局混乱 | 标题作用在「当前坐标系」上 | pyplot 函数作用于全局当前对象;建议全用面向对象写法 |
plt.subplots(2, 2) 后用 axes[0] 索引 | axes[0] 返回一整行(仍是数组),再 .plot() 报 AttributeError: 'numpy.ndarray' object has no attribute 'plot' | 二维数组要用 axes[0, 0] 两个下标 |
章末练习
基础
- 写出
plt.subplots()的返回值分别是什么对象,并各用一个例子说明它们的职责。 - 用面向对象写法画一张 5 个点的折线图,标题为「练习图」,保存为
p2.png。 - 用
plt.subplots(1, 2)画两张并排折线图,左图[1, 2, 3]、右图[3, 2, 1]。
提高
- 分别用 pyplot 写法和面向对象写法画同一张图,并把两份代码贴在一起对比,指出各自「当前状态」如何管理。
- 用
plt.subplots(2, 2, figsize=(8, 6))画 2×2 网格,四个子图分别画正弦、余弦、正切、常数的曲线(用np.linspace生成 x)。
挑战
- 把
plt.subplots(1, 2)返回的axes用for循环遍历,给每个子图画一条不同的线,并统一设置网格,体会axes.flat的用法。 - 思考并实验:
fig, ax = plt.subplots()之后再调用fig.add_subplot()会发生什么?plt.gca()返回的是什么?用代码验证你的猜想。
章末自测
每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。
plt.subplots()的返回值是?- A. 只有一个
Figure - B.
(fig, ax)两个对象 - C. 只有一个
Axes - D. 一个列表
- A. 只有一个
- 谁负责容纳多个坐标系?
- A.
Axes - B.
Figure - C.
Grid - D.
Ticks
- A.
- 真正画数据线的是哪个对象的方法?
- A.
Figure.plot - B.
Axes.plot - C.
plt.figure - D.
Canvas.plot
- A.
fig, axes = plt.subplots(1, 2)后,type(axes)是?- A.
Axes - B.
Figure - C.
numpy.ndarray - D.
list
- A.
- 2×2 网格里右下角的子图用哪个下标访问?
- A.
axes[2] - B.
axes[1, 1] - C.
axes[2, 2] - D.
axes[0, 1]
- A.
figsize=(8, 6)的单位是?- A. 像素
- B. 英寸
- C. 厘米
- D. 点(pt)
- 面向对象写法里,设置标题应调用?
- A.
plt.title() - B.
ax.set_title() - C.
fig.title() - D.
ax.title()
- A.
axes.plot([1, 2, 3])且axes来自subplots(1, 2)时,结果是?- A. 正常画出折线
- B. 只画第一个子图
- C.
AttributeError: 'numpy.ndarray' object has no attribute 'plot' - D. 画出两条线
- pyplot 状态机写法中,
plt.plot作用在哪个对象上?- A. 所有 Figure
- B. 「当前」Figure 里的「当前」Axes
- C. 新创建的 Axes
- D. 无对象
- 关于两种写法,正确的说法是?
- A. 面向对象写法不支持多子图
- B. pyplot 写法不支持
savefig - C. 两者都能画出同样的图,面向对象更适合复杂布局
- D. 必须二选一,不能混用
