NumPy: 文件IO

难度:⭐⭐ | 关键词np.save, np.load, np.savez, np.savetxt, np.loadtxt, np.memmap

Bob 每次运行模型花 10 分钟重新生成数据。Alice:"np.save 第一次存成 .npy,以后 np.load 0.1 秒加载。二进制比文本快 10 倍、小 3 倍。"


1. save / load — 二进制单数组

np.save(file, arr) 将数组存为 .npy 二进制文件;np.load(file) 读回。

参数 说明
file 文件路径,自动补 .npy 后缀
arr 要保存的 ndarray
allow_pickle np.load 参数,是否允许反序列化 pickle,默认 False

(1) .npy 格式特点

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:save / load(难度⭐)

PYTHON
import numpy as np

data = np.arange(12).reshape(3, 4)

np.save("my_array.npy", data)

loaded = np.load("my_array.npy")
print(loaded)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(np.array_equal(data, loaded))  # True

# save 会自动补 .npy 后缀,下面两行等价
np.save("my_array", data)      # 实际保存为 my_array.npy
np.save("my_array.npy", data)  # 同上
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

2. savez / savez_compressed — 多数组打包

np.savez(file, *args, **kwargs) 将多个数组存入一个 .npz 文件(ZIP 格式)。

函数 说明 压缩
np.savez 多数组打包为 .npz
np.savez_compressed 同上,但启用压缩

(1) savez 用法

PYTHON
import numpy as np

# 位置参数 → 自动命名 arr_0, arr_1, ...
a = np.arange(6).reshape(2, 3)
b = np.array([1, 2, 3])
np.savez("data.npz", a, b)
# 查看键名:np.load("data.npz").files → ['arr_0', 'arr_1']

# 关键字参数 → 用键名访问
weights = np.array([0.5, 0.3])
biases = np.array([0.1, 0.2])
np.savez("params.npz", weights=weights, biases=biases)
loaded = np.load("params.npz")
print(loaded["weights"])  # [0.5 0.3]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:savez 多数组(难度⭐)

PYTHON
import numpy as np

weights = np.random.randn(3, 4)
biases = np.random.randn(4)
labels = np.array([0, 1, 2, 1])

np.savez("model.npz", weights=weights, biases=biases, labels=labels)

data = np.load("model.npz")
print(data.files)                    # ['weights', 'biases', 'labels']
print(data["weights"].shape)         # (3, 4)
print(data["biases"].shape)          # (4,)

data.close()

# 或用 with 语句
with np.load("model.npz") as data:
    w = data["weights"]
    b = data["biases"]
    print(np.array_equal(w, weights))  # True
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(2) savez vs savez_compressed 对比

PYTHON
import numpy as np
import os

arr = np.random.randn(10000, 100)

np.savez("uncompressed.npz", arr=arr)
np.savez_compressed("compressed.npz", arr=arr)

size_raw = os.path.getsize("uncompressed.npz")
size_zip = os.path.getsize("compressed.npz")
print(f"Uncompressed: {size_raw / 1e6:.2f} MB")
print(f"Compressed:   {size_zip / 1e6:.2f} MB")
print(f"Ratio:        {size_zip / size_raw:.2%}")
# Uncompressed: ~8.00 MB
# Compressed:   ~7.60 MB  (随机数据压缩率低)
# Ratio:        ~95%

# 规律数据压缩效果显著
pattern = np.arange(1_000_000)
np.savez("pat_raw.npz", arr=pattern)
np.savez_compressed("pat_zip.npz", arr=pattern)
print(f"Pattern compressed ratio: "
      f"{os.path.getsize('pat_zip.npz') / os.path.getsize('pat_raw.npz'):.2%}")
# Pattern compressed ratio: ~10%
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

3. savetxt / loadtxt — 文本格式

np.savetxt(fname, X, fmt='%.18e', delimiter=' ') 保存为文本;np.loadtxt(fname, delimiter=None) 读回。

(1) 格式对比

格式 扩展名 保留元信息 读写速度 文件大小 可读性
.npy .npy ✅ dtype/shape ⚡ 极快 🟢 小
.npz .npz ✅ 多数组 ⚡ 快 🟡 中
.txt .txt 🐢 慢 🔴 大
.csv .csv 🐢 慢 🔴 大

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:savetxt / loadtxt(难度⭐⭐)

PYTHON
import numpy as np

data = np.array([[1.0, 2.5, 3.7],
                 [4.2, 5.8, 6.1],
                 [7.3, 8.9, 9.0]])

np.savetxt("data.txt", data)

np.savetxt("data.csv", data, fmt="%.2f", delimiter=",")

loaded_txt = np.loadtxt("data.txt")
loaded_csv = np.loadtxt("data.csv", delimiter=",")
print(loaded_csv)
# [[1.   2.5  3.7 ]
#  [4.2  5.8  6.1 ]
#  [7.3  8.9  9.  ]]

np.savetxt("header.csv", data, fmt="%.2f", delimiter=",",
           header="col1,col2,col3", comments="")
loaded_skip = np.loadtxt("header.csv", delimiter=",", skiprows=1)
print(np.array_equal(loaded_skip, data))  # True
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(2) save vs savetxt 性能对比

PYTHON
import numpy as np
import time
import os

big = np.random.randn(100000, 50)

t0 = time.perf_counter()
np.save("perf.npy", big)
t_save = time.perf_counter() - t0

t0 = time.perf_counter()
np.savetxt("perf.txt", big, fmt="%.6f")
t_txt = time.perf_counter() - t0

print(f"save:    {t_save:.3f}s  size={os.path.getsize('perf.npy')/1e6:.1f}MB")
print(f"savetxt: {t_txt:.3f}s  size={os.path.getsize('perf.txt')/1e6:.1f}MB")
print(f"Text is {t_txt/t_save:.0f}x slower, "
      f"{os.path.getsize('perf.txt')/os.path.getsize('perf.npy'):.1f}x larger")
# save:    ~0.05s  size=38.1MB
# savetxt: ~2.50s  size=135.0MB
# Text is ~50x slower, ~3.5x larger
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

4. genfromtxt — 缺失值处理

np.genfromtxt(fname, delimiter=None, missing_values=None, filling_values=None)loadtxt 的增强版,能处理缺失值和异构列。

(1) genfromtxt vs loadtxt

特性 np.loadtxt np.genfromtxt
缺失值处理 ❌ 遇到缺失直接报错 ✅ 可指定填充值
列名读取 names=True
指定列类型 dtype=None 自动推断
速度 较快 较慢(功能更多)
适用场景 干净整齐的文本 有缺失/表头的 CSV

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:genfromtxt 缺失值(难度⭐⭐)

PYTHON
import numpy as np
from io import StringIO

csv = """name,score,age
Alice,85,20
Bob,,22
Charlie,90,
Carol,78,21
"""

data = np.genfromtxt(StringIO(csv), delimiter=",", names=True,
                     dtype=None, encoding=None, missing_values="",
                     filling_values=-1)
print(data)
# [('Alice', 85., 20) ('Bob', -1., 22) ('Charlie', 90., -1) ('Carol', 78., 21)]

print(data["name"])   # ['Alice' 'Bob' 'Charlie' 'Carol']
print(data["score"])  # [85. -1. 90. 78.]
print(data["age"])    # [20 22 -1 21]

data2 = np.genfromtxt(StringIO(csv), delimiter=",", names=True,
                      dtype=None, encoding=None, missing_values="",
                      filling_values=("unknown", 0, -1))
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(2) CSV 读写实战

PYTHON
import numpy as np

rng = np.random.default_rng(42)
records = np.column_stack([
    np.arange(1, 6),
    rng.integers(60, 100, size=5),
    rng.integers(18, 25, size=5)
])
np.savetxt("students.csv", records, fmt="%d", delimiter=",",
           header="id,score,age", comments="")

data = np.loadtxt("students.csv", delimiter=",", skiprows=1,
                  dtype=[("id", int), ("score", int), ("age", int)])
print(data["id"])    # [1 2 3 4 5]
print(data["score"]) # [88 78 91 65 86]
print(data["age"])   # [21 23 20 24 19]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

5. fromfile / tofile — 底层二进制

ndarray.tofile(fid)np.fromfile(file, dtype) 是最底层的二进制读写,不保存元信息

特性 save/load tofile/fromfile
保存元信息 ✅ dtype/shape ❌ 裸数据
跨平台安全 ⚠️ 字节序可能不同
适用场景 通用 已知 dtype/shape 的固定格式
PYTHON
import numpy as np

arr = np.arange(6, dtype=np.float64)

arr.tofile("raw.bin")

loaded = np.fromfile("raw.bin", dtype=np.float64)
print(loaded)  # [0. 1. 2. 3. 4. 5.]

# shape 信息丢失,需要手动 reshape
arr2d = np.arange(12).reshape(3, 4)
arr2d.tofile("raw2d.bin")
loaded2d = np.fromfile("raw2d.bin", dtype=np.int64).reshape(3, 4)
print(np.array_equal(arr2d, loaded2d))  # True
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

6. memmap — 大文件映射

np.memmap(filename, dtype, mode, shape) 将磁盘文件映射为数组,不一次性读入内存

(1) 大文件策略对比

策略 内存占用 速度 适用场景
np.load 全部加载到内存 数据量 < 可用内存
np.memmap 按需加载,虚拟内存 🟡 数据量 > 内存
分块读写 手动控制 🟡 超大数据流水线
HDF5 (h5py) 按需加载 + 切片 多维超大数据

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:memmap 大文件(难度⭐⭐⭐)

PYTHON
import numpy as np
import os

filename = "bigdata.dat"
shape = (100000, 1000)  # 1 亿 float64 ≈ 800MB

fp = np.memmap(filename, dtype=np.float64, mode="w+", shape=shape)

fp[:10] = np.arange(10).reshape(10, 1) * np.ones(1000)
del fp  # 关闭映射,数据写入磁盘

print(f"File size: {os.path.getsize(filename) / 1e6:.0f} MB")

fp_read = np.memmap(filename, dtype=np.float64, mode="r", shape=shape)
print(fp_read[0, :5])  # [0. 0. 0. 0. 0.]
print(fp_read[5, :5])  # [5. 5. 5. 5. 5.]

fp_rw = np.memmap(filename, dtype=np.float64, mode="r+", shape=shape)
fp_rw[0, 0] = 999.0
fp_rw.flush()

fp_check = np.memmap(filename, dtype=np.float64, mode="r", shape=shape)
print(fp_check[0, 0])  # 999.0

del fp_read, fp_rw, fp_check
os.remove(filename)
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(2) IO 决策树

100%
graph TB
    A["需要保存/加载数组"] --> B{"需要可读性?"}
    B -->|是| C{"有缺失值?"}
    B -->|否| D{"单个还是多个数组?"}
    C -->|否| E["savetxt / loadtxt"]
    C -->|是| F["genfromtxt"]
    D -->|单个| G{"数据 > 内存?"}
    D -->|多个| H{"需要压缩?"}
    G -->|否| I["save / load (.npy)"]
    G -->|是| J["memmap"]
    H -->|否| K["savez (.npz)"]
    H -->|是| L["savez_compressed (.npz)"]

    style A fill:#4CAF50,color:#fff
    style I fill:#2196F3,color:#fff
    style J fill:#FF9800,color:#fff
    style K fill:#9C27B0,color:#fff
    style L fill:#E91E63,color:#fff
    style E fill:#00BCD4,color:#fff
    style F fill:#795548,color:#fff
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

7. 综合示例:数据持久化流水线

Alice 演示完整的数据 IO 流程:生成 → 保存二进制 → 读取 → 读写 CSV → memmap 处理超大数据。

PYTHON
import numpy as np
import os
from io import StringIO

rng = np.random.default_rng(seed=42)

# ========================================
# Part 1: 生成数据并二进制保存
# ========================================
print("=== Part 1: Binary Save/Load ===\n")

features = rng.normal(0, 1, size=(10000, 50))
labels = rng.integers(0, 10, size=10000)

np.save("features.npy", features)
np.save("labels.npy", labels)

feat_loaded = np.load("features.npy")
print(f"Features shape: {feat_loaded.shape}")
print(f"Data matches: {np.array_equal(feat_loaded, features)}")

# ========================================
# Part 2: savez 打包多数组
# ========================================
print("\n=== Part 2: savez Multiple Arrays ===\n")

np.savez("dataset.npz", features=features, labels=labels)
np.savez_compressed("dataset_z.npz", features=features, labels=labels)

size_raw = os.path.getsize("dataset.npz")
size_zip = os.path.getsize("dataset_z.npz")
print(f"Raw:   {size_raw / 1e6:.2f} MB")
print(f"Compressed: {size_zip / 1e6:.2f} MB ({size_zip/size_raw:.0%} of raw)")

with np.load("dataset.npz") as d:
    print(f"Arrays in npz: {d.files}")
    print(f"Features match: {np.array_equal(d['features'], features)}")

# ========================================
# Part 3: CSV 读写
# ========================================
print("\n=== Part 3: CSV Read/Write ===\n")

sample = features[:5, :4]
header = "f0,f1,f2,f3"
np.savetxt("sample.csv", sample, fmt="%.4f", delimiter=",",
           header=header, comments="")

csv_data = np.loadtxt("sample.csv", delimiter=",", skiprows=1)
print(f"CSV shape: {csv_data.shape}")
print(f"CSV matches: {np.allclose(csv_data, sample)}")

# ========================================
# Part 4: genfromtxt 读取含缺失 CSV
# ========================================
print("\n=== Part 4: genfromtxt with Missing ===\n")

csv_missing = """id,score,grade
1,85,A
2,,B
3,92,
4,78,C
"""

data = np.genfromtxt(StringIO(csv_missing), delimiter=",",
                     names=True, dtype=None, encoding=None,
                     filling_values=(0, -1, "X"))
print(f"IDs:    {data['id']}")
print(f"Scores: {data['score']}")
print(f"Grades: {data['grade']}")

# ========================================
# Part 5: memmap 处理超大数据
# ========================================
print("\n=== Part 5: memmap Large Data ===\n")

big_shape = (50000, 500)
big_file = "bigdata.dat"

fp = np.memmap(big_file, dtype=np.float64, mode="w+", shape=big_shape)
fp[:] = rng.standard_normal(big_shape)
fp.flush()
del fp

fp_read = np.memmap(big_file, dtype=np.float64, mode="r", shape=big_shape)
col_mean = fp_read[:, 0].mean()
print(f"File size: {os.path.getsize(big_file)/1e6:.0f} MB")
print(f"Column 0 mean: {col_mean:.4f} (expect ~0)")
del fp_read

for f in ["features.npy", "labels.npy", "dataset.npz",
          "dataset_z.npz", "sample.csv", "bigdata.dat"]:
    if os.path.exists(f):
        os.remove(f)
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

❓ 常见问题

Q .npy 和 .npz 有什么区别?
A .npy 存单个数组,.npz 是 ZIP 格式打包多个数组。np.save.npynp.savez.npz.npz 加载后返回类似字典的 NpzFile 对象,通过键名访问各数组。
Q 为什么推荐用二进制而不是文本?
A 二进制(.npy/.npz)保留 dtype 和 shape 元信息、读写快约 10~50 倍、文件小约 3 倍。文本格式(.txt/.csv)丢失元信息且需解析字符串,唯一优势是人类可读和与 Excel 等工具兼容。
Q genfromtxt 和 loadtxt 怎么选?
A 数据干净无缺失用 loadtxt(更快);有缺失值、需要列名、或列类型不一致时用 genfromtxtgenfromtxt 功能更多但速度更慢。
Q memmap 什么时候用?
A 当数据量超过可用内存时。memmap 利用操作系统的虚拟内存机制按需加载,只在访问时读取对应磁盘区域。适合处理超大数组的局部操作,但随机访问较多时仍然会频繁换页,性能下降。
Q savez_compressed 压缩效果如何?
A 取决于数据特征。随机浮点数压缩率低(约 90%~95%),规律整数/低精度数据压缩率高(可到 10%~30%)。压缩/解压需要额外 CPU 时间,存储空间紧张时使用,否则 savez 更快。
Q tofile/fromfile 和 save/load 的区别?
A tofile/fromfile 是裸二进制,不保存 dtype 和 shape,读回时必须手动指定且需知道原始形状,还有字节序风险。save/load 内含完整元信息,安全且推荐日常使用。

📖 小节


📝 作业

(1) 三个数组存入 .npz

  1. 创建 3 个数组:weights = rng.randn(10, 5)biases = rng.randn(5)labels = np.array([0,1,2,3,4])
  2. np.savez 保存为 model.npz
  3. np.load 读回,验证 3 个数组与原始一致

(2) loadtxt 读取 CSV

  1. np.savetxt 将以下数据保存为 scores.csv(含表头 math,english,science):
TEXT 📖 仅展示
90,85,88
78,92,80
95,88,93
  1. np.loadtxt 读取(跳过表头),计算每科平均分

(3) .npy vs .txt 大小对比

  1. 生成 rng.random(size=(10000, 100)) 的大数组
  2. 分别用 np.savenp.savetxt 保存
  3. 比较两种文件的大小,计算文本文件是二进制的几倍
  4. 比较读写耗时(用 time.perf_counter
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏