Skip to content

使用 pickle 模块读写二进制文件

在 Python 中,读取和写入二进制文件(.dat.pickle 文件扩展名)的协议已在内置的 pickle 模块中实现。

pickle 是 Python 特定的二进制文件格式,它不仅可以用于存储二进制数据,还可以存储任何 Python 对象。

将数据结构(列表、字典等)和代码对象(类、函数等)转换为可以存储在二进制文件中的字节的过程称为序列化 (Serialization)pickling。此二进制文件可以存储在磁盘上或共享,并且以后可以通过 Python 反序列化 (deserialization 或 unpickling) 并使用。

存储数据 (Dumping Data / Pickling)

dump() 函数可用于将任何数据或对象的 pickled 表示形式写入文件。

语法pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)

其中:

  • obj 是要写入的对象
  • file 是一个打开的文件对象(以二进制写入 wb 或追加 ab 模式打开)。
  • protocol 是可选的,指定使用的 pickle 协议版本。
python
>>> import pickle
>>> l = [["Anita","Maths",83],
...      ["Amar","Maths",95],
...      ["Ani","Maths",90]]
>>> with open("marks.dat", "wb") as f: # 使用 with 确保关闭
...     pickle.dump(l, f)
...
>>>

加载数据 (Loading Data / Unpickling)

load() 函数可用于从文件中读取对象的 pickled 表示形式。

语法pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)

其中 file 是一个打开的文件对象(以二进制读取模式 rb 打开)。

python
>>> import pickle
>>> try:
...     with open("marks.dat", "rb") as f:
...         l = pickle.load(f)
...     print(l)
... except FileNotFoundError:
...     print("文件未找到")
...
[['Anita', 'Maths', 83], ['Amar', 'Maths', 95], ['Ani', 'Maths', 90]]

安全警告: pickle 模块不安全。它很容易被构造恶意 pickle 数据来在解封 (unpickling) 过程中执行任意代码。永远不要解封来自不受信任或未经验证来源的数据。

示例:遍历二进制文件

编写一个程序,执行以下记录保存活动:

  • 接受一些学生成绩作为输入,并将数据写入二进制文件
  • 从文件中读取所有数据
  • 向文件中添加更多数据
  • 显示文件的全部内容

代码

python
import pickle

#写入初始数据
try:
    with open("marks.dat", "wb") as f:
        flag = 1
        while flag != 0:
            name = input("输入学生姓名: ")
            subject = input("输入科目: ")
            marks = float(input("输入分数: "))
            rec = [name, subject, marks]
            pickle.dump(rec, f) # 一次写入一个记录
            try:
                flag = int(input("输入 1 继续添加,输入 0 终止: "))
            except ValueError:
                flag = 0 # 输入无效时终止
except IOError as e:
    print(f"写入文件时出错: {e}")


#读取文件内容并显示
print("\n文件当前内容:")
records = [] # 用于存储所有记录的列表
try:
    with open("marks.dat", "rb") as f:
        while True: # 循环读取直到文件末尾
            try:
                rec = pickle.load(f)
                records.append(rec)
                print(rec)
            except EOFError: # 到达文件末尾时中断循环
                print("已到达文件末尾")
                break
except FileNotFoundError:
    print("文件 'marks.dat' 未找到。")
except IOError as e:
    print(f"读取文件时出错: {e}")
except pickle.UnpicklingError as e:
    print(f"解封数据时出错: {e}")


#添加更多数据
print("\n添加更多数据...")
try:
    with open("marks.dat", "ab") as f: # 使用 'ab' 追加模式
        flag = 1
        while flag != 0:
            name = input("输入学生姓名: ")
            subject = input("输入科目: ")
            marks = float(input("输入分数: "))
            rec = [name, subject, marks]
            pickle.dump(rec, f)
            try:
                flag = int(input("输入 1 继续添加,输入 0 终止: "))
            except ValueError:
                flag = 0
except IOError as e:
    print(f"追加写入文件时出错: {e}")


#显示追加后的所有内容
print("\n追加数据后的文件内容:")
all_records_after_append = []
try:
    with open("marks.dat", "rb") as f:
        while True:
            try:
                rec = pickle.load(f)
                all_records_after_append.append(rec)
                print(rec)
            except EOFError:
                print("已到达文件末尾")
                break
except FileNotFoundError:
    print("文件 'marks.dat' 未找到。")
except IOError as e:
    print(f"读取文件时出错: {e}")
except pickle.UnpicklingError as e:
    print(f"解封数据时出错: {e}")

(注意: 原代码在读取和写入时混合,且读取逻辑需要改进以正确处理多个 pickle 对象。这里改进了逻辑,分离读写,并使用循环和异常处理来读取所有对象直到 EOFError。同时添加了基本的错误处理。)

输入/输出示例

输入学生姓名: Anita
输入科目: Maths
输入分数: 83
输入 1 继续添加,输入 0 终止: 1
输入学生姓名: Amar
输入科目: Maths
输入分数: 95
输入 1 继续添加,输入 0 终止: 0

文件当前内容:
['Anita', 'Maths', 83.0]
['Amar', 'Maths', 95.0]
已到达文件末尾

添加更多数据...
输入学生姓名: Akash
输入科目: Science
输入分数: 92
输入 1 继续添加,输入 0 终止: 1
输入学生姓名: Ira
输入科目: Science
输入分数: 99
输入 1 继续添加,输入 0 终止: 0

追加数据后的文件内容:
['Anita', 'Maths', 83.0]
['Amar', 'Maths', 95.0]
['Akash', 'Science', 92.0]
['Ira', 'Science', 99.0]
已到达文件末尾