走进一间专业大厨的厨房,你会发现台面上永远挂着两样东西:一只银亮的小勺,和一口冒着热气的蒸锅。小勺用于“精准点料”——哪道菜差一点盐,伸过去轻轻一抖;蒸锅用于“整锅料理”——食材码好,一笼蒸齐,火候均匀。Jupyter 里的魔法命令,恰好就是这两样工具的化身:以 % 开头的行魔法精准作用于一行代码,以 %% 开头的单元魔法席卷整个单元格。魔法命令不是 Python 的语法,而是 IPython 内核额外赠送的一套“厨具墙”——%timeit 是厨房秒表,%run 是解冻预制菜,%matplotlib inline 是透明出菜窗,%debug 是裂缝放大镜。学会这套工具,你的数据分析就像大厨颠勺一样行云流水,从此告别复制粘贴和“凭感觉的慢”。
对 AI 从业者而言,Jupyter 魔法命令是日常实验的“效率杠杆”——用最小代价测性能、跑脚本、调 bug、出图表,把宝贵时间留给真正的模型与数据思考。
目录
可视化演示
展示行魔法与单元魔法的执行差异、%timeit性能测量结果对比,以及常用魔法命令的工作流关系。
本节配有交互动效(进入视口后自动加载,约 3KB)。
行魔法 vs 单元魔法 行魔法(%):作用于单行代码,如%timeit x=1 单元魔法(%%):作用于整个单元格,可执行多行代码 %cmd (1行) vs %%cmd (多行) 前缀%控制行级,%%控制单元格级,提升交互分析效率
魔法总览:%号小勺与%%大蒸锅
想象你走进一间专业大厨的厨房。灶台上一排料理盆,大厨手握两样工具:一把银光小勺和一口冒热气的蒸锅。小勺的作用是“精准点料”——哪道菜差一点盐,伸过去轻轻一抖;蒸锅的作用是“整锅料理”——食材码好,一笼蒸齐,火候均匀。Jupyter 里的魔法命令,正是这样两种工具:单点的行魔法,和整锅的单元魔法。
Jupyter 笔记本就是一间厨房,每个代码单元格是一个料理盆,普通 Python 代码是待下锅的食材。魔法命令是以 % 开头的“特殊锅铲”,它们不属于 Python 语法,而是 IPython 内核额外提供的便捷功能。行魔法以单个 % 开头,只管它所在的那一行;单元魔法以 %% 开头,管的是整个单元格中的所有代码。
在 Jupyter 中输入 %lsmagic,IPython 会列出一份完整的“厨具清单”:所有行魔法与单元魔法一目了然。行魔法的典型语法是 %timeit sum(range(100))——魔法名后紧跟它要处理的“食材”;单元魔法的典型语法是 %%timeit 独占单元格第一行,第二行起是被计时的代码正文。魔法命令本质上是 IPython 内核中注册的函数,调用它会将拆解后的参数作为字符串传入。
所以当你看到 % 与 %% 时,只需想一下大厨的手势:单点调味用小勺,整笼蒸煮靠大锅。选对工具,厨房效率自然翻倍。
- 行魔法范围$S_{\text{line}}$ 表示行魔法的作用范围——它只覆盖单元格中的第 $k$ 行代码 $L_k$。就像小勺只蘸到汤面那一点。
- 单元魔法范围$S_{\text{cell}}$ 表示单元魔法的作用范围,从第 $1$ 行 $L_1$ 一直到第 $n$ 行 $L_n$ 全部覆盖。如同蒸锅让整屉食材同时受热。
- 包含关系由于 $S_{\text{line}}$ 只含一行,而 $S_{\text{cell}}$ 包含全部行,所以前者是后者的子集,记作 $S_{\text{line}} \subseteq S_{\text{cell}}$。
计时魔法:厨房秒表
大厨的台面上永远挂着一只秒表。判断“焯水三十秒”还是“炖足两小时”,要用时间说话,而不是靠感觉。数据科学的厨房里,%timeit 就是那只秒表——它不会模糊地说“这段代码好像有点慢”,而是精确报出“这段代码每次循环耗时 3.45 微秒”。
Python 里想测一段代码的耗时,最笨的办法是在代码前后各取一次时间戳相减。可这样测出的单次时间包含操作系统调度噪声、CPU 变频、垃圾回收等干扰,极不稳定。%timeit 的做法更像大厨尝菜:一勺不准,就多尝几勺,取最好的那一口。它会自动反复运行被测代码,攒出一批样本,再统计平均耗时与最佳耗时。
%timeit 只测一行,%%timeit 测整个单元。它默认在“足够长的时间”里反复跑,自动选择循环次数;也可用 -n 1000 指定每轮循环次数、-r 7 指定重复轮数。运行结束打印形如 3.45 µs ± 123 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)——µs 是微秒,ns 是纳秒,± 后是标准差。通常我们更看重 best of,因为最小值最接近代码本身的纯净耗时。
有了秒表,大厨就能把“凭感觉的慢”变成“可量化的慢”:先测旧配方再测新配方,谁快谁慢一目了然。%timeit 正是你在性能调优时最忠实的厨房秒表。
- 单次耗时样本$t_i$ 是第 $i$ 次循环测得的时间。计时从 $t_0$ 到 $t_1$,则 $t_i = t_1 - t_0$。每次运行都会受系统调度影响,所以需要多次采样。
- 求平均耗时$\bar{t}$ 将 $N$ 次样本全部加总,再除以 $N$。求和符号 $\displaystyle\sum_{i=1}^{N}$ 表示从第 1 个到第 $N$ 个依次累加。
- 求最佳耗时$t_{\text{best}}$ 取 $N$ 次样本中的最小值,写作 $\min$。因为调度噪声只会让测量“变慢”而几乎不会“变快”,最小值最接近代码的真实耗时。
运行魔法:冰箱预制菜
大厨有个习惯:把反复使用的秘制高汤预先做好、装罐、冻进冰箱。客人点单时不用从头熬,取出罐头下锅加热即可。Jupyter 里的 .py 脚本,就是预先存在冰箱里的“半成品菜”。
%run script.py 就是那口“下锅加热”的锅。它把外部 Python 脚本送入当前 notebook 的全局命名空间执行——和 !python script.py(在 shell 子进程里跑)有本质区别:%run 执行后,脚本里定义的函数、变量会像你自己在单元格里敲过一样留在内存中,接下来可以直接引用。
%run 还支持传参:%run script.py a b c 会让脚本里的 sys.argv 变成 ['script.py', 'a', 'b', 'c'],就像脚本是主角,参数列表是递给主角的台词本。另一个命令 %load script.py 更“温柔”:它不执行脚本,只把文件内容读入当前单元格编辑区,让你先审阅再决定是否运行。
于是大厨的工作流变成:秘方(工具函数)提前存成 .py 文件,每天开工时 %run 一声,整间厨房的准备工作瞬间就绪——省去了复制粘贴与重复手动操作。
- 列表首元素$\text{script.py}$ 永远占据 $argv[0]$,这是 Python 运行时自动填充的被执行脚本名,无论你传不传参数它都在。
- 后续参数$\text{arg}_1$ 到 $\text{arg}_m$ 是用户在
%run后以空格分隔传入的 $m$ 个参数。脚本内部用sys.argv[1]、sys.argv[2]取出使用。 - 执行方式
%run将脚本放入 notebook 全局命名空间执行,等价于把脚本内容直接粘贴进单元格运行,变量随之保留。
绘图魔法:透明出菜窗
中餐厨房和大堂之间常有一面透明出菜窗。菜炒好后,厨师颠勺、装盘、往窗口一推,客人立刻看见热气腾腾的成品。数据分析里,图表就是那道菜,而 %matplotlib inline 就是餐厅的透明出菜窗。
默认情况下,matplotlib 会把图表弹到独立 GUI 窗口;在 Jupyter 里这样做既不便也不利于记录复现。执行一次 %matplotlib inline 后,matplotlib 切换到内联后端(inline backend),所有 figure 会被渲染成 PNG 图片并直接嵌入 cell 的输出区域,与代码、文字一起留存在笔记本中。
更贴心的是,内联模式下通常无需手动 plt.show()——figure 被创建并绘制后会自动展示。还可设置 %config InlineBackend.figure_format='retina' 让高分屏更清晰,或切换 %matplotlib notebook(可交互缩放)与 %matplotlib widget(控件化图表)。出菜窗可选透明玻璃、毛玻璃或带滑窗,不同窗口服务不同场合。
当你敲下 %matplotlib inline,Jupyter 厨房的透明窗便徐徐升起。之后每次画图,数据之美都能端到盘中、被一眼看到——这正是交互式数据分析最迷人的体验之一。
- figure 画布$figure$ 是 matplotlib 的绘图画布对象,承载数据点、坐标轴、图例等全部图形元素。
- 内联渲染$\xrightarrow{\text{inline 渲染}}$ 表示 inline 后端将矢量界面的 figure 栅格化为 PNG 图片,这一转换由内核自动完成。
- 嵌入输出PNG 被写入 notebook 的 HTML 输出区,成为单元格输出的可视部分,无需额外调用
plt.show()。
调试魔法:裂痕放大镜
再好的厨师也会失手。盛汤时碗底一条隐裂纹,肉眼察觉不到,可汤一满就渗。有经验的厨师会拿起放大镜,顺着裂纹找源头。%debug 就是你在代码异常后的那枚放大镜。
当某段代码抛出异常,notebook 会打印一长串 traceback(回溯信息)。此时执行 %debug,IPython 会基于最近一次异常自动启动交互式调试器。你不再需要对着异常信息猜,而是直接“进入”出错现场逐层勘查。
调试器里有一套“现场勘查命令”:w 打印当前调用栈(where),u / d 在栈帧之间上下移动,p 变量名 打印该层变量的值,q 退出调试器。若想更自动,提前执行 %pdb on——此后每次抛异常,放大镜都会自动递到你手里。
学会了 %debug 和 %pdb,面对满屏红色报错,你不再头皮发麻,而是从容地顺着裂纹一层层往下摸——直到在某层栈帧里,看见那只“打碎碗的手”。
- 栈帧序列$\sigma_1$ 到 $\sigma_k$ 表示调用链从最外层到最内层共 $k$ 层,按“谁调用了谁”的顺序排列成列表。
- 帧的三要素每个栈帧 $\sigma_j$ 记录出问题处的文件 $\text{file}_j$、行号 $\text{line}_j$ 和所在函数 $\text{func}_j$,三者共同确定“当前站在哪一层、看到了什么”。
- 逐层定位调试器用
u(up)向上层栈帧移动、d(down)向下层移动,$k$ 越大说明调用链越深,源头往往在最内层帧。
自定义魔法:专属厨具
大厨用久了,不免嫌标准厨具不趁手:市售的勺子深浅固定,偏偏自己的汤要少半勺盐。怎么办?自己打一把。Jupyter 允许你注册自定义魔法,让“专属厨具”出现在你的魔法清单里。
定义自定义魔法最简方式是用装饰器。在单元格中执行以下代码后,hello 便成为一个新的行魔法:
之后在任意单元格输入 %hello 小明,就会看到返回值 Hello, 小明!。魔法函数接收 line 参数,即 %hello 后面跟的整段文本,运行结果会自动显示在输出区。
除了 register_line_magic,还有 register_cell_magic(单元魔法,接收 line 与 cell 两个参数)、register_line_cell_magic(行、单元皆可)。也可用 ipython.register_magic_function(func, magic_kind='line', magic_name='mycmd') 手动注册。自定义魔法非常适合把重复样板(连接数据库、生成报表、格式化输出)收进一个小命令。
一把趁手的勺能颠出好味道。当你在魔法清单里攒满专属厨具,后续每次分析都如行云流水——这就是“工欲善其事,必先利其器”的真意。
- 注册器$\text{register}$ 是 IPython 提供的注册入口,它接收两个关键输入——魔法名字 $\text{name}$ 和处理函数 $f_{\text{fn}}$。
- 命名绑定注册后,notebook 中输入
%name 参数等价于调用 $f_{\text{fn}}(\text{参数})$,参数以字符串形式传入line。 - 装饰器等价物
@register_line_magic语法糖等价于手工执行register('hello', hello),省去了函数名与魔法名不一致的烦恼。\@ \text{register\_line\_magic} \equiv \text{register}(\text{name}, f_{\text{fn}})
from IPython.core.magic import register_line_magic @register_line_magicdef hello(line): name = line.strip() or 'world' return f"Hello, {name}!"
效率锦囊:顺手工具墙
大厨的灶台边,工具永远放在固定位置:计时器挂右边、抹布挂左边、调料从左到右排成一条线。Jupyter 也有一面“顺手工具墙”,一票高频魔法命令随取随用。
盘点常用行魔法:%lsmagic 列出全部魔法;%who / %whos 查看当前命名空间变量(whos 带详细信息);%env 查看与设置环境变量;%reset 清空命名空间,给厨房做大扫除;%history 查看历史命令;%time 做单次轻量计时;%pwd 看当前目录、%cd 切换目录。单元魔法方面,%%writefile 文件名 能把单元格内容写入文件,%%bash 直接在 shell 中执行。
这一票工具用得好,能显著压缩“从想法到结果”的路径:不必打开新终端、不必离开 notebook、不必复制粘贴。魔法的价值在于把多次击键压缩成一次回车,把散落的琐碎操作收拢成一套肌肉记忆。
当这些工具在指尖排成队列,你的 Jupyter 厨房便已“万物齐备”。剩下的事,就是把食材(数据)倒入锅中,安心等待一顿美味的分析大餐出锅。
- 效率定义$\eta$ 代表单位时间内真正有价值产出的多少。分子 $P_{\text{产出}}$ 是完成的分析成果,分母是投入的操作次数 $N_{\text{操作}}$ 与等待时间 $T_{\text{等待}}$。
- 压缩操作次数魔法命令把“新建终端、写循环、复制结果”等多次击键合并为一条
%命令,使 $N_{\text{操作}}$ 大幅下降。 - 压缩等待时间
%timeit快速定位慢代码、%%writefile免去手动存文件,使 $T_{\text{等待}}$ 同步下降,效率进一步提升。
AI 应用场景
特征工程性能对比
阶段:数据预处理与特征工程
解决的问题:特征工程中,pandas 逐行循环与向量化计算的性能差距巨大,但“凭感觉”往往选不出更优方案。用 %%timeit 对两种实现分别计时,用数据说话,快速选定高性价比特征处理方案。
具体案例:某推荐系统团队需要从 2000 万条行为日志中提取用户活跃时段特征。旧代码用 for 循环遍历 DataFrame 耗时 8.2 秒,向量化版本仅 0.3 秒。借助 %%timeit 的量化对比,团队果断重构,线上训练数据流水线提速 27 倍。
深度学习训练脚本调试
阶段:模型训练与调优
解决的问题:训练脚本庞大且经常阶段性修改,重启 Jupyter 内核后所有变量丢失,重新 %run 又怕改错地方。用 %run 在 notebook 命名空间运行训练脚本,脚本定义的模型与历史变量全部保留;loss 突变时用 %debug 进入异常现场,逐层检查梯度与数据形状。
具体案例:一位算法工程师训练 Transformer 时遇到 NaN loss,异常栈指向自定义损失函数。执行 %debug 后用 p 打印中间张量,发现是 log 输入出现负数;一行归一化修复后,loss 恢复正常。
自动化报表生成
阶段:结果可视化与汇报
解决的问题:每周都要产出相同结构的数据图表,重复代码多且易出错。将图表绘制逻辑封装为自定义魔法 %weekly_report,配合 %matplotlib inline 一键内联出图,将周报制作从 40 分钟压缩到 2 分钟。
具体案例:某数据分析师用 register_line_magic 注册 %report 魔法,传入数据集名与指标列名即可自动生成趋势图、分布直方图和汇总表,并直接内嵌显示在 notebook 中供团队评审。
Python 演示
import timefrom functools import wraps def my_timeit(repeat=3, number=1000): """模拟 %timeit 的计时装饰器。""" def deco(fn): @wraps(fn) def wrapper(*args, **kwargs): samples = [] for _ in range(repeat): t0 = time.perf_counter() for _ in range(number): fn(*args, **kwargs) t1 = time.perf_counter() samples.append((t1 - t0) / number) best = min(samples) mean = sum(samples) / len(samples) print(f"{fn.__name__}: " f"{best * 1e6:.3f} us per loop " f"(mean {mean * 1e6:.3f} us, " f"best of {repeat} runs)") return fn(*args, **kwargs) return wrapper return deco @my_timeit(repeat=5, number=2000)def sum_small(): return sum(range(100)) @my_timeit(repeat=5, number=2000)def sum_large(): return sum(range(10000)) def main(): sum_small() sum_large() if __name__ == "__main__": main()
%timeit 的核心逻辑——重复运行、取平均耗时与最佳耗时,帮助理解计时魔法背后的原理。def my_timeit(repeat=3, number=1000):装饰器工厂,repeat对应-r轮数,number对应-n每轮循环次数。samples.append((t1 - t0) / number)每轮测出总耗时后除以number,得到单次循环的平均耗时,存入样本列表。best = min(samples)取样本最小值作为“最佳耗时”,这正是%timeit输出中best of的含义。mean = sum(samples) / len(samples)计算所有轮次的算术平均,对应输出中的mean ± std的均值部分。@my_timeit(repeat=5, number=2000)装饰器用法,等价于sum_small = my_timeit(...)(sum_small),让函数自动获得计时能力。if __name__ == "__main__":保证脚本被直接运行时才调用main(),被%run导入时不会自动执行。
常见误区
误区我在一个单元格里写 `%%timeit sum(range(100))` 并且后面还跟着其他代码,结果报错了,是不是 Magic 命令不稳定?
正解%% 是单元魔法,必须独占单元格第一行,且第一行之后的所有代码才是被测对象。正确写法是:第一行写 %%timeit,第二行起写被测代码。若把被测代码也塞进第一行,等价于让蒸锅去点料——自然翻车。行魔法用单 %,单元魔法用双 %%,位置规则是硬性的。
为何常见:初学时容易把 %% 当作“更强版本的 %”,忽略了行魔法与单元魔法本质上是两套不同作用范围的机制。
误区我的代码跑了 3 秒,为什么 `%timeit` 只显示 3.45 微秒?是不是统计错了?
正解%timeit 报告的是“单次循环”的耗时,且取的是 best of(多轮中最优值)。3 秒是整个程序的总时间,而被测的 sum(range(100)) 单次执行只需 3.45 微秒。若想测整段代码总耗时,可用 %time 或 %%time(单次计时),两者用途不同,不能混为一谈。
为何常见:看到 per loop 字样容易忽略“每循环”的语义,把单次耗时误当成整体耗时,尤其是在被测代码本身包含循环时最容易混淆。
误区`%run model.py` 和 `!python model.py` 不都是跑一个脚本吗?我选一个用就行。
正解两者执行环境完全不同。!python 在 shell 子进程中运行,脚本定义的变量、函数在子进程结束后全部消失,notebook 里看不到;%run 则在当前 notebook 的全局命名空间中执行,脚本里定义的变量、函数、类都会保留下来,可以直接在后续单元格中使用。需要脚本产物时,必须用 %run。
为何常见:两者都能打印脚本输出,表面效果相似,初学者往往看不到“命名空间共享”这一隐藏差异。
误区我已经写了 `%matplotlib inline`,为什么还要在代码里调 `plt.show()`?我少写一行是不是图就不显示了?
正解%matplotlib inline 是设置 matplotlib 的后端为“内联渲染”,它改变的是图的输出目的地(嵌入 notebook 而非弹窗)。开启内联后,figure 直接显示,通常不再需要 plt.show();若代码里仍调用 show(),多数情况下也安全,只是多余。真正要关心的不是 show,而是后端配置与渲染格式。
为何常见:%matplotlib inline 与 plt.show() 都出现在“画图流程”里,初学者容易把“后端设置”和“显示行为”当成同一件事。
误区自定义魔法是不是要写一个复杂的类,或者放到某个配置文件里才能在 Jupyter 中生效?
正解完全不需要。只需在任意单元格中定义普通函数并加上装饰器,如 @register_line_magic,该魔法立刻在当前内核中生效。函数的 line 参数接收 %名前 的文本,返回值自动显示。装饰器本质是把函数注册到 IPython 的魔法表,不涉及类、不涉及文件,三行代码即可完成。
为何常见:受“面向对象”思维影响,觉得一切功能都要类封装;实际上魔法注册只是“函数 + 装饰器”的轻量约定。
面试题
Q1. 下列哪个是行魔法?
- A.%%timeit
- B.%%bash
- C.%timeit← 正确答案
- D.%%writefile
答案:C。行魔法以单个 % 开头,只作用于一行代码,%timeit 是典型代表;%%timeit、%%bash、%%writefile 都是双百分号开头的单元魔法,作用于整个单元格。就像小勺 vs 蒸锅,一个点料一个整蒸。
Q2. 在 Jupyter 中查看所有可用魔法命令列表,应该用哪个命令?
- A.%lsmagic← 正确答案
- B.%who
- C.%env
- D.%history
答案:A。%lsmagic 会列出全部行魔法与单元魔法清单;%who 查看变量、%env 查看环境变量、%history 查看历史命令,三者都只是“厨房里某扇柜门”,而 %lsmagic 是整面工具墙的索引。
Q3. %run script.py 与 !python script.py 的主要区别是什么?
- A.前者运行速度更快
- B.前者在当前 notebook 命名空间执行,变量共享且保留← 正确答案
- C.两者没有任何区别
- D.前者只能运行 Python 2 脚本
答案:B。%run 把脚本放入 notebook 全局命名空间执行,脚本定义的变量、函数在之后单元格中可直接使用;!python 在子进程运行,变量随之消亡。这就像“预制菜倒回自家锅里”和“端去邻居家灶上烧”的区别——食材虽然都热了,但留在谁家厨房完全不同。
Q4. %timeit 输出的统计结果中,哪个指标更能反映代码本身的性能?
- A.mean(平均耗时)
- B.std(标准差)
- C.best of(多轮最优耗时)← 正确答案
- D.所有循环的总耗时
答案:C。操作系统的调度噪声只会让计时“变慢”而极少“变快”,因此多轮样本中的最小值最接近代码不带干扰的真实耗时。平均耗时会受偶发噪声拖累,标准差反映波动性而非绝对性能,总耗时则取决于循环次数。
Q5. 某单元格运行抛出异常后,你想进入交互式调试器逐层检查变量,应该执行哪个命令?
- A.%pdb
- B.%debug← 正确答案
- C.%run
- D.%reset
答案:B。%debug 基于最近一次异常启动交互式调试器,进入出错现场逐帧检查;%pdb on 是设置“异常自动进入调试器”的开关,而非手动进入的命令;%run 是执行脚本,%reset 是清空环境。放大镜要在裂缝出现后再举,%debug 正是那个“举起放大镜”的动作。
Q6. 关于 %%timeit,下列说法正确的是?
- A.它只对所在单元格的第一行代码计时
- B.它必须独占单元格首行,对后续全部代码计时← 正确答案
- C.它用来测量代码的圈复杂度
- D.它是 matplotlib 的绘图命令
答案:B。%%timeit 是单元魔法,第一行写 %%timeit 后,从第二行起的全部代码都是被测对象。正如蒸锅一开,整屉食材同时受热——不是只蒸第一块。
Q7. 以下哪些是单元魔法?
- A.%%timeit← 正确答案
- B.%%writefile← 正确答案
- C.%run
- D.%%bash← 正确答案
答案:ABD。%%timeit(单元计时)、%%writefile(单元格内容写入文件)、%%bash(在 shell 中执行单元格内容)都是双百分号单元魔法;%run 是单百分号行魔法。判断标准看前缀:单 % 一行,双 %% 一单元。
Q8. 关于 %matplotlib inline,下列说法正确的有?
- A.图表会内嵌到单元格输出区域← 正确答案
- B.开启后一般无需显式调用 plt.show()← 正确答案
- C.图表会被强制弹出到系统独立窗口
- D.它是 matplotlib 之外的一个新绘图库
答案:AB。%matplotlib inline 将 matplotlib 后端切换为内联渲染,figure 自动渲染为 PNG 嵌入 notebook,因此通常无需 plt.show();它不会弹窗,也并非新绘图库,只是 matplotlib 的“出菜窗口”配置。
Q9. 以下哪些方式可以注册自定义魔法?
- A.使用 @register_line_magic 装饰器← 正确答案
- B.使用 @register_cell_magic 装饰器← 正确答案
- C.在终端执行 %lsmagic 动态创建
- D.修改 notebook 的 .ipynb 文件扩展名
答案:AB。@register_line_magic 注册行魔法,@register_cell_magic 注册单元魔法,二者都是 IPython 提供的装饰器注册入口;%lsmagic 只是查看清单不会创建魔法,改文件扩展名与注册无关。这就像打厨具要用锻铁炉(注册器),而不是靠清点工具清单。
Q10. 进入 %debug 调试器后,想打印当前调用栈应该输入哪个命令?
- A.p
- B.w← 正确答案
- C.u
- D.d
答案:B。w(where)打印当前调用栈,p 打印变量表达式,u / d 在栈帧间上下移动。调用栈就是那串“谁叫了谁”的裂缝纹路,w 让你看清整条纹路的走向,再决定往哪层帧移动。