Chapter 07 / 10

魔法命令(Magic Commands)

IPython 特有的 % 和 %% 命令,让性能测试、文件操作、Shell 命令融入 Notebook 工作流

什么是魔法命令

魔法命令(Magic Commands)是 IPython 内核提供的特殊命令,以 %%% 开头,提供了许多普通 Python 代码无法直接完成的功能。

行魔法(Line Magic):%

只对当前行生效。

%timeit sum(range(10000))
%pwd
%ls
%who

单元格魔法(Cell Magic):%%

对整个单元格生效,必须在第一行。

%%time
for i in range(10**6):
    pass

性能测试:%timeit 和 %%time

性能测试是 Jupyter 最常用的魔法命令场景:

%timeit 命令输出截图
%timeit 对 sum(range(10000)) 进行性能测试,输出平均时间和标准差(7次运行,每次10000循环)
# %timeit — 自动运行多次取平均
%timeit sum(range(10000))
63.4 µs ± 425 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

# 可以指定运行次数和循环次数
%timeit -n 100 -r 5 sum(range(10000))

# %%time — 整个单元格的一次性计时
%%time
data = list(range(10**7))
total = sum(data)
CPU times: user 212 ms, sys: 45 ms, total: 257 ms
Wall time: 258 ms
%timeit vs %%time
%timeit 自动多次执行取均值,适合微基准测试(microsecond 级别);%%time 只执行一次,适合较慢的操作(second 级别)。

文件系统操作

# 显示当前工作目录
%pwd
'/private/tmp/jupyter_demo'

# 列出文件(等同于 ls 命令)
%ls
demo.ipynb  data.csv  images/

# 切换目录
%cd /Users/mi/projects

# 创建目录
%mkdir new_folder

数据可视化:%matplotlib

这是数据科学中最重要的魔法命令之一:

# 让图表内联显示在 Notebook 中(推荐)
%matplotlib inline

# 交互式图表(可以缩放、平移)
%matplotlib widget

# 在外部窗口显示(传统方式)
%matplotlib qt

# 之后所有 matplotlib 图表都会内联显示
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2*np.pi, 100)
plt.plot(x, np.sin(x))
plt.title('y = sin(x)')
plt.show()  # 图表直接显示在输出区域
TIP %matplotlib inline 通常放在 Notebook 的第一个单元格,和 import 语句一起。一旦设置,整个会话都有效,无需每个单元格都加。

运行外部脚本:%run

# 运行外部 Python 脚本,脚本中的变量进入当前命名空间
%run analysis.py

# 运行并传入参数
%run script.py arg1 arg2

# 运行另一个 Notebook(作为普通 Python 脚本执行)
%run helper_notebook.ipynb

写入文件:%%writefile

%%writefile hello.py
# 这个单元格的内容会被写入 hello.py 文件
def greet(name):
    return f"Hello, {name}!"

if __name__ == "__main__":
    print(greet("World"))

Writing hello.py

Shell 命令:! 前缀

除了魔法命令,还可以用 ! 前缀直接运行 Shell 命令:

# 直接运行 Shell 命令
!pip install seaborn
!git status
!cat data.csv | head -5

# 捕获 Shell 命令输出到 Python 变量
files = !ls *.csv
print(files)  # 返回字符串列表

变量查看

# 查看当前所有变量
%who
x  y  data  model

# 查看变量及其类型
%whos
Variable   Type    Data/Info
-----------|---------|---------
x          int      42
y          float    3.14
data       list     n=100

# 删除变量
%reset_selective x   # 删除变量 x
%reset                # 删除所有变量(会确认提示)

查看历史与保存

# 查看执行历史
%history
%history -n 1-10   # 第 1-10 次执行的代码

# 保存到文件
%save my_session.py 1-10   # 将第 1-10 次执行的代码保存

# 查看所有可用的魔法命令
%lsmagic

常用魔法命令汇总

命令说明
%timeit自动多次运行,性能基准测试
%%time单元格一次性计时
%matplotlib inline设置图表内联显示
%run file.py运行外部脚本
%%writefile f.py将单元格内容写入文件
%pwd / %cd / %ls文件系统操作
%who / %whos查看当前变量
%reset清除所有变量
%history查看执行历史
%load file.py将文件内容加载到单元格
%%html将单元格内容渲染为 HTML
%%bash将单元格内容作为 Bash 执行

魔法命令的工作原理

IPython 扩展机制
魔法命令是 IPython 内核的扩展功能,不是 Python 语言的一部分。IPython 在执行代码前先检查行首是否有 % 或 %% 前缀,如果有则将该行交给对应的魔法命令处理器,而不是 Python 解释器。这意味着魔法命令只在 Jupyter/IPython 环境中有效——将 Notebook 导出为 .py 脚本后,魔法命令行会失效(导出时会带注释标记)。
%timeit 的统计方法
%timeit 通过多次执行代码(默认 7 轮,每轮执行足够多次使总时间约 0.2s)取最快的 3 次的平均值,以消除系统负载波动的影响。输出格式:"X µs ± Y µs per loop (mean ± std. dev. of 7 runs, Z loops each)"。这比 %%time(单次执行)更精确,适用于微基准测试(microsecond 到 millisecond 级别的操作)。对于耗时超过 1 秒的操作,%timeit 和 %%time 结果差异不大。
Shell 命令的变量传递
Shell 命令(! 前缀)可以通过 $variable 语法引用 Python 变量,也可以将输出赋值给 Python 变量:output = !ls -la(返回输出行组成的列表)。注意:! Shell 命令在子进程中执行,!cd /path 不会改变 Python 进程的当前目录(子进程的目录变化不影响父进程)。需要改变 Notebook 的工作目录时,必须使用 %cd /path 魔法命令。
%load_ext autoreload:开发自己的 Python 包时必备

修改了自己的 Python 包代码后,默认情况下 Kernel 不会重新加载已导入的模块。每次修改都要重启 Kernel 非常低效。autoreload 扩展解决了这个问题:

%load_ext autoreload
%autoreload 2   # 模式 2:每次执行 cell 前,自动重载所有修改过的模块

这样修改包代码后,只需重新运行相关的 cell,无需重启 Kernel。

自定义魔法命令

你可以创建自己的魔法命令来封装常用操作:

from IPython.core.magic import register_line_magic, register_cell_magic

# 定义行魔法命令
@register_line_magic
def hello(line):
    """自定义魔法命令:%hello name"""
    return f"Hello, {line}!"

# 使用:%hello World → 输出 "Hello, World!"

# 定义单元格魔法命令
@register_cell_magic
def count_lines(line, cell):
    """统计单元格代码行数"""
    lines = cell.strip().split('\n')
    return f"This cell has {len(lines)} lines"

# 使用:%%count_lines 后跟多行代码
魔法命令的性能陷阱

魔法命令虽然方便,但有性能开销。%timeit 本身会多次执行代码,不适合有副作用的操作(如写文件、发网络请求)。!shell_command 每次都启动新的子进程,频繁调用会很慢——如果需要多次执行 Shell 命令,考虑用 subprocess 模块或 %%bash 单元格魔法一次性执行多条命令。

本章小结

本章核心要点