Python文件操作

Published on 2017 - 01 - 31

文件输入/输出

数据持久化最简单的类型是普通文件,有时也叫平面文件(flat file)。它仅仅是在一个文件名下的字节流,把数据从一个文件读入内存,然后从内存写入文件。Python 很容易实现这些文件操作,它模仿熟悉的和流行的 Unix 系统的操作。

读写一个文件之前需要打开它:

fileobj = open(filename, mode)

下面是对该 open() 调用的简单解释:

  • fileobj 是 open() 返回的文件对象;
  • filename 是该文件的字符串名;
  • mode 是指明文件类型和操作的字符串。

mode 的第一个字母表明对其的操作。

  • r 表示读模式。
  • w 表示写模式。如果文件不存在则新创建,如果存在则重写新内容。
  • x 表示在文件不存在的情况下新创建并写文件。
  • a 表示如果文件存在,在文件末尾追加写内容。

mode 的第二个字母是文件类型:

  • t(或者省略)代表文本类型;
  • b 代表二进制文件。

打开文件之后就可以调用函数来读写数据,之后的例子会涉及。

最后需要关闭文件。

接下来在一个程序中用 Python 字符串创建一个文件,然后返回。

使用write()写文本文件

出于一些原因,我们没有太多的关于狭义相对论的五行打油诗(limerick)。下面这首作为源数据:

>>> poem = '''There was a young lady named Bright,
... Whose speed was far faster than light;
... She started one day
... In a relative way,
... And returned on the previous night.'''
>>> len(poem)
150

以下代码将整首诗写到文件 'relativity' 中:

>>> fout = open('relativity', 'wt')
>>> fout.write(poem)
150
>>> fout.close()

函数 write() 返回写入文件的字节数。和 print() 一样,它没有增加空格或者换行符。同样,你也可以在一个文本文件中使用 print():

>>> fout = open('relativity', 'wt')
>>> print(poem, file=fout)
>>> fout.close()

这就产生了一个问题:到底是使用 write() 还是 print() ? print() 默认会在每个参数后面添加空格,在每行结束处添加换行。 在之前的例子中,它在文件 relativity 中默认添加了一个换行。为了使 print() 与 write() 有同样的输出,传入下面两个参数。

  • sep 分隔符:默认是一个空格 ' '
  • end 结束字符:默认是一个换行符 '\n'

除非自定义参数,否则 print() 会使用默认参数。在这里,我们通过空字符串替换 print() 添加的所有多余输出:

>>> fout = open('relativity', 'wt')
>>> print(poem, file=fout, sep='', end='')
>>> fout.close()

如果源字符串非常大,可以将数据分块,直到所有字符被写入:

>>> fout = open('relativity', 'wt')
>>> size = len(poem)
>>> offset = 0
>>> chunk = 100
>>> while True:
...     if offset > size:
...          break
...     fout.write(poem[offset:offset+chunk])
...     offset += chunk
...
100
50
>>> fout.close()

第一次写入 100 个字符,然后写入剩下的 50 个字符。

如果 'relativity' 文件已经存在,使用模式 x 可以避免重写文件:

>>> fout = open('relativity', 'xt')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileExistsError: [Errno 17] File exists: 'relativity'

可以加入一个异常处理:

>>> try:
...     fout = open('relativity', 'xt')
...     fout.write('stomp stomp stomp')
... except FileExistsError:
...     print('relativity already exists!. That was a close one.')
...
relativity already exists!. That was a close one.

使用read()、readline()或者readlines()读文本文件

你可以按照下面的示例那样,使用不带参数的 read() 函数一次读入文件的所有内容。但在读入文件时要格外注意,1 GB 的文件会用到相同大小的内存。

>>> fin = open('relativity', 'rt' )
>>> poem = fin.read()
>>> fin.close()
>>> len(poem)
150

同样也可以设置最大的读入字符数限制 read() 函数一次返回的大小。下面一次读入 100 个字符,然后把每一块拼接成原来的字符串 poem:

>>> poem = ''
>>> fin = open('relativity', 'rt' )
>>> chunk = 100
>>> while True:
...         fragment = fin.read(chunk)
...         if not fragment:
...             break
...         poem += fragment
...
>>> fin.close()
>>> len(poem)
150

读到文件结尾之后,再次调用 read() 会返回空字符串(''),if not fragment 条件被判为 False。此时会跳出 while True 的循环。 当然,你也能使用 readline() 每次读入文件的一行。在下一个例子中,通过追加每一行拼接成原来的字符串 poem:

>>> poem = ''
>>> fin = open('relativity', 'rt' )
>>> while True:
...         line = fin.readline()
...         if not line:
...             break
...         poem += line
...
>>> fin.close()
>>> len(poem)
150

对于一个文本文件,即使空行也有 1 字符长度(换行字符 '\n'),自然就会返回 True。当文件读取结束后,readline()(类似 read())同样会返回空字符串,也被 while True 判 为 False。

读取文本文件最简单的方式是使用一个迭代器(iterator),它会每次返回一行。这和之前的例子类似,但代码会更短:

>>> poem = ''
>>> fin = open('relativity', 'rt' )
>>> for line in fin:
...         poem += line
...
>>> fin.close()
>>> len(poem)
150

前面所有的示例最终都返回单个字符串 poem。函数 readlines() 调用时每次读取一行,并返回单行字符串的列表:

>>> fin = open('relativity', 'rt' )
>>> lines = fin.readlines()
>>> fin.close()
>>> print(len(lines), 'lines read')
5 lines read
>>> for line in lines:
...         print(line, end='')
...
There was a young lady named Bright,
Whose speed was far faster than light;
She started one day
In a relative way,
And returned on the previous night.>>>

之前我们让 print() 去掉每行结束的自动换行,因为前面的四行都有换行标志,而最后一行没有,所以导致解释器的提示符 >>> 出现在最后一行的最右边。

使用write()写二进制文件

如果文件模式字符串中包含 'b',那么文件会以二进制模式打开。这种情况下,读写的是字节而不是字符串。

我们手边没有二进制格式的诗,所以直接在 0~255 产生 256 字节的值:

>>> bdata = bytes(range(0, 256))
>>> len(bdata)
256

以二进制模式打开文件,并且一次写入所有的数据:

>>> fout = open('bfile', 'wb')
>>> fout.write(bdata)
256
>>> fout.close()

再次,write() 返回到写入的字节数。

对于文本,也可以分块写二进制数据:

>>> fout = open('bfile', 'wb')
>>> size = len(bdata)
>>> offset = 0
>>> chunk = 100
>>> while True:
...         if offset > size:
...             break
...         fout.write(bdata[offset:offset+chunk])
...         offset += chunk
...
100
100
56
>>> fout.close()

使用read()读二进制文件

下面简单的例子只需要用 'rb' 打开文件即可:

>>> fin = open('bfile', 'rb')
>>> bdata = fin.read()
>>> len(bdata)
256
>>> fin.close()

使用with自动关闭文件

如果你忘记关闭已经打开的一个文件,在该文件对象不再被引用之后 Python 会关掉此文件。这也就意味着在一个函数中打开文件,没有及时关闭它,但是在函数结束时会被关掉。然而你可能会在一直运行中的函数或者程序的主要部分打开一个文件,应该强制剩下的所有写操作完成后再关闭文件。

Python 的上下文管理器(context manager)会清理一些资源,例如打开的文件。它的形式为 with expression as variable:

>>> with open('relativity', 'wt') as fout:
...         fout.write(poem)
...

完成上下文管理器的代码后,文件会被自动关闭。

使用seek()改变位置

无论是读或者写文件,Python 都会跟踪文件中的位置。函数 tell() 返回距离文件开始处的字节偏移量。函数 seek() 允许跳转到文件其他字节偏移量的位置。这意味着可以不用从头读取文件的每一个字节,直接跳到最后位置并只读一个字节也是可行的。

对于这个例子,使用之前写过的 256 字节的二进制文件 'bfile':

>>> fin = open('bfile', 'rb')
>>> fin.tell()
0

使用 seek() 读取文件结束前最后一个字节:

>>> fin.seek(255)
255

一直读到文件结束:

>>> bdata = fin.read()
>>> len(bdata)
1
>>> bdata[0]
255

seek() 同样返回当前的偏移量。

用第二个参数调用函数 seek():seek(offset,origin)。

  • 如果 origin 等于 0(默认为 0),从开头偏移 offset 个字节;
  • 如果 origin 等于 1,从当前位置处偏移 offset 个字节;
  • 如果 origin 等于 2,距离最后结尾处偏移 offset 个字节。

这些值也在标准 os 模块中被定义:

>>> import os
>>> os.SEEK_SET
0
>>> os.SEEK_CUR
1
>>> os.SEEK_END
2

所以,我们可以用不同的方法读取最后一个字节:

>>> fin = open('bfile', 'rb')

文件结尾前的一个字节:

>>> fin.seek(-1, 2)
255
>>> fin.tell()
255

一直读到文件结尾:

>>> bdata = fin.read()
>>> len(bdata)
1
>>> bdata[0]
255

在调用 seek() 函数时不需要额外调用 tell()。前面的例子只是想说明两个函数都可以返回同样的偏移量。

下面是从文件的当前位置寻找的例子:

>>> fin = open('bfile', 'rb')

接下来的例子返回最后两个字节:

>>> fin.seek(254, 0)
254
>>> fin.tell()
254

在此基础上前进一个字节:

>>> fin.seek(1, 1)
255
>>> fin.tell()
255

最后一直读到文件结尾:

>>> bdata = fin.read()
>>> len(bdata)
1
>>> bdata[0]
255

这些函数对于二进制文件都是极其重要的。当文件是 ASCII 编码(每个字符一个字节)时,也可以使用它们,但是计算偏移量会是一件麻烦事。其实,这些都取决于文本的编码格式,最流行的编码格式(例如 UTF-8)每个字符的字节数都不尽相同。

结构化的文本文件

对于简单的文本文件,唯一的结构层次是间隔的行。然而有时候需要更加结构化的文本,用于后续使用的程序保存数据或者向另外一个程序传送数据。

结构化的文本有很多格式,区别它们的方法如下所示。

  • 分隔符,比如 tab('\t')、逗号(',')或者竖线('|')。逗号分隔值(CSV)就是这样的例子。
  • '<' 和 '>' 标签,例如 XML 和 HTML。
  • 标点符号,例如 JavaScript Object Notation(JSON)。
  • 缩进,例如 YAML(即 YAML Ain't Markup Language 的缩写),要了解更多可以去搜索。
  • 混合的,例如各种配置文件。

每一种结构化文件格式都能够被至少一种 Python 模块读写。

CSV

带分隔符的文件一般用作数据交换格式或者数据库。你可以人工读入 CSV 文件,每一次读取一行,在逗号分隔符处将每行分开,并添加结果到某些数据结构中,例如列表或者字典。但是,最好使用标准的 csv 模块,因为这样切分会得到更加复杂的信息。

  • 除了逗号,还有其他可代替的分隔符:'|' 和 '\t' 很常见。
  • 有些数据会有转义字符序列,如果分隔符出现在一块区域内,则整块都要加上引号或者在它之前加上转义字符。
  • 文件可能有不同的换行符,Unix 系统的文件使用 '\n',Microsoft 使用 '\r\n',Apple 之前使用 '\r' 而现在使用 '\n'。
  • 在第一行可以加上列名。

首先读和写一个列表的行,每一行包含很多列:

>>> import csv
>>> villains = [
...         ['Doctor', 'No'],
...         ['Rosa', 'Klebb'],
...         ['Mister', 'Big'],
...         ['Auric', 'Goldfinger'],
...         ['Ernst', 'Blofeld'],
...     ]
>>> with open('villains', 'wt') as fout:  # 一个上下文管理器
...     csvout = csv.writer(fout)
...     csvout.writerows(villains)

于是创建了包含以下几行的文件 villains:

Doctor,No
Rosa,Klebb
Mister,Big
Auric,Goldfinger
Ernst,Blofeld

现在,我们来重新读这个文件:

>>> import csv
>>> with open('villains', 'rt') as fin:  # 一个上下文管理器
...     cin = csv.reader(fin)
...     villains = [row for row in cin]  # 使用列表推导式
...
>>> print(villains)
[['Doctor', 'No'], ['Rosa', 'Klebb'], ['Mister', 'Big'],
['Auric', 'Goldfinger'], ['Ernst', 'Blofeld']]

我们利用函数 reader() 创建的结构,它在通过 for 循环提取到的 cin 对象中构建每一行。

使用 reader() 和 writer() 的默认操作。每一列用逗号分开;每一行用换行符分开。

数据可以是字典的集合(a list of dictionary),不仅仅是列表的集合(a list of list)。这次使用新函数 DictReader() 读取文件 villains,并且指定每一列的名字:

>>> import csv
>>> with open('villains', 'rt') as fin:
# fieldnames是指字典的Key,如果不指定的默认是第一行的单词
...     cin = csv.DictReader(fin, fieldnames=['first', 'last'])
...     villains = [row for row in cin]
...
>>> print(villains)
[{'last': 'No', 'first': 'Doctor'},
{'last': 'Klebb', 'first': 'Rosa'},
{'last': 'Big', 'first': 'Mister'},
{'last': 'Goldfinger', 'first': 'Auric'},
{'last': 'Blofeld', 'first': 'Ernst'}]

下面使用新函数 DictWriter() 重写 CSV 文件,同时调用 writeheader() 向 CSV 文件中第一行写入每一列的名字:

import csv
villains = [
    {'first': 'Doctor', 'last': 'No'},
    {'first': 'Rosa', 'last': 'Klebb'},
    {'first': 'Mister', 'last': 'Big'},
    {'first': 'Auric', 'last': 'Goldfinger'},
    {'first': 'Ernst', 'last': 'Blofeld'},
    ]
with open('villains', 'wt') as fout:
    cout = csv.DictWriter(fout, ['first', 'last'])
    cout.writeheader()
    cout.writerows(villains)

于是创建了具有标题行的新文件 villains:

first,last
Doctor,No
Rosa,Klebb
Mister,Big
Auric,Goldfinger
Ernst,Blofeld

回过来再读取写入的文件,忽略函数 DictReader() 调用的参数 fieldnames,把第一行的值(first,last)作为列标签,和字典的键做匹配:

>>> import csv
>>> with open('villains', 'rt') as fin:
...     cin = csv.DictReader(fin)
...     villains = [row for row in cin]
...
>>> print(villains)
[{'last': 'No', 'first': 'Doctor'},
{'last': 'Klebb', 'first': 'Rosa'},
{'last': 'Big', 'first': 'Mister'},
{'last': 'Goldfinger', 'first': 'Auric'},
{'last': 'Blofeld', 'first': 'Ernst'}]

XML

带分隔符的文件仅有两维的数据:行和列。如果你想在程序之间交换数据结构,需要一种方法把层次结构、序列、集合和其他的结构编码成文本。

XML 是最突出的处理这种转换的标记(markup)格式,它使用标签(tag)分隔数据,如下面的示例文件 menu.xml 所示:

<?xml version="1.0"?>
<menu>
  <breakfast hours="7-11">
    <item price="$6.00">breakfast burritos</item>
    <item price="$4.00">pancakes</item>
  </breakfast>
  <lunch hours="11-3">
    <item price="$5.00">hamburger</item>
  </lunch>
  <dinner hours="3-10">
    <item price="8.00">spaghetti</item>
  </dinner>
</menu>

以下是 XML 的一些重要特性:

  • 标签以一个 < 字符开头,例如示例中的标签 menu、breakfast、lunch、dinner 和 item;
  • 忽略空格;
  • 通常一个开始标签(例如 <menu>)跟一段其他的内容,然后是最后相匹配的结束标签,例如 </menu>
  • 标签之间是可以存在多级嵌套的,在本例中,标签 item 是标签 breakfast、lunch 和 dinner 的子标签,反过来,它们也是标签 menu 的子标签;
  • 可选属性(attribute)可以出现在开始标签里,例如 price 是 item 的一个属性;
  • 标签中可以包含值(value),本例中每个 item 都会有一个值,比如第二个 breakfast item 的 pancakes;
  • 如果一个命名为 thing 的标签没有内容或者子标签,它可以用一个在右尖括号的前面添加斜杠的简单标签所表示,例如 <thing/> 代替开始和结束都存在的标签 <thing></thing>
  • 存放数据的位置可以是任意的——属性、值或者子标签。例如也可以把最后一个 item 标签写作 <item price ="$8.00" food ="spaghetti"/>

XML 通常用于数据传送和消息,它存在一些子格式,如 RSS 和 Atom。工业界有许多定制化的 XML 格式,例如金融领域

XML 的灵活性导致出现了很多方法和性能各异的 Python 库。

在 Python 中解析 XML 最简单的方法是使用 ElementTree,下面的代码用来解析 menu.xml 文件以及输出一些标签和属性:

下面的代码用来解析 menu.xml 文件以及输出一些标签和属性:

>>> import xml.etree.ElementTree as et
>>> tree = et.ElementTree(file='menu.xml')
>>> root = tree.getroot()
>>> root.tag
'menu'
>>> for child in root:
...         print('tag:', child.tag, 'attributes:', child.attrib)
...         for grandchild in child:
...             print('\ttag:', grandchild.tag, 'attributes:', grandchild.attrib)
...
tag: breakfast attributes: {'hours': '7-11'}
    tag: item attributes: {'price': '$6.00'}
    tag: item attributes: {'price': '$4.00'}
tag: lunch attributes: {'hours': '11-3'}
    tag: item attributes: {'price': '$5.00'}
tag: dinner attributes: {'hours': '3-10'}
    tag: item attributes: {'price': '8.00'}
>>> len(root)     # 菜单选择的数目
3
>>> len(root[0])  # 早餐项的数目
2

对于嵌套列表中的每一个元素,tag 是标签字符串,attrib 是它属性的一个字典。ElementTree 有许多查找 XML 导出数据、修改数据乃至写入 XML 文件的方法,它的文档 中有详细的介绍。

其他标准的 Python XML 库如下。

xml.dom

JavaScript 开发者比较熟悉的文档对象模型(DOM)将 Web 文档表示成层次结构,它会把整个 XML 文件载入到内存中,同样允许你获取所有的内容。

xml.sax

简单的 XML API 或者 SAX 都是通过在线解析 XML,不需要一次载入所有内容到内存中,因此对于处理巨大的 XML 文件流是一个很好的选择。

JSON

JavaScript Object Notation(JSON,http://www.json.org)是源于 JavaScript 的当今很流行的数据交换格式,它是 JavaScript 语言的一个子集,也是 Python 合法可支持的语法。对于 Python 的兼容性使得它成为程序间数据交换的较好选择。

不同于众多的 XML 模块,Python 只有一个主要的 JSON 模块 json(名字容易记忆)。下面的程序将数据编码成 JSON 字符串,然后再把 JSON 字符串解码成数据。用之前 XML 例子的数据构建 JSON 的数据结构:

>>> menu = \
... {
... "breakfast": {
...         "hours": "7-11",
...         "items": {
...                 "breakfast burritos": "$6.00",
...                 "pancakes": "$4.00"
...                 }
...         },
... "lunch" : {
...         "hours": "11-3",
...         "items": {
...                 "hamburger": "$5.00"
...                 }
...         },
... "dinner": {
...         "hours": "3-10",
...         "items": {
...                 "spaghetti": "$8.00"
...                 }
...         }
... }
.

接下来使用 dumps() 将 menu 编码成 JSON 字符串(menu_json):

>>> import json
>>> menu_json = json.dumps(menu)
>>> menu_json
'{"dinner": {"items": {"spaghetti": "$8.00"}, "hours": "3-10"},
"lunch": {"items": {"hamburger": "$5.00"}, "hours": "11-3"},
"breakfast": {"items": {"breakfast burritos": "$6.00", "pancakes":
"$4.00"}, "hours": "7-11"}}'

现在反过来使用 loads() 把 JSON 字符串 menu_json 解析成 Python 的数据结构(menu2):

>>> menu2 = json.loads(menu_json)
>>> menu2
{'breakfast': {'items': {'breakfast burritos': '$6.00', 'pancakes':
'$4.00'}, 'hours': '7-11'}, 'lunch': {'items': {'hamburger': '$5.00'},
'hours': '11-3'}, 'dinner': {'items': {'spaghetti': '$8.00'}, 'hours': '3-10'}}

menu 和 menu2 是具有相同键值的字典,和标准的字典用法一样,得到的键的顺序是不尽相同的。

YAML

和 JSON 类似,YAML(http://www.yaml.org)同样有键和值,但主要用来处理日期和时间这样的数据类型。标准的 Python 库没有处理 YAML 的模块,因此需要安装第三方库 yaml 操作数据。load() 将 YAML 字符串转换为 Python 数据结构,而 dump() 正好相反。

下面的 YAML 文件 mcintyre.yaml 包含加拿大诗人 James McIntyre 的两首诗的信息:

name:
  first: James
  last: McIntyre
dates:
  birth: 1828-05-25
  death: 1906-03-31
details:
  bearded: true
  themes: [cheese, Canada]
books:
  url: http://www.gutenberg.org/files/36068/36068-h/36068-h.htm
poems:
  - title: 'Motto'
    text: |
      Politeness, perseverance and pluck,
      To their possessor will bring good luck.
  - title: 'Canadian Charms'
    text: |
      Here industry is not in vain,
      For we have bounteous crops of grain,
      And you behold on every field
      Of grass and roots abundant yield,
      But after all the greatest charm
      Is the snug home upon the farm,
      And stone walls now keep cattle warm.

类似于 true、false、on 和 off 的值可以转换为 Python 的布尔值。整数和字符串转换为 Python 等价的。其他语法创建列表和字典:

>>> import yaml
>>> with open('mcintyre.yaml', 'rt') as fin:
>>>     text = fin.read()
>>> data = yaml.load(text)
>>> data['details']
{'themes': ['cheese', 'Canada'], 'bearded': True}
>>> len(data['poems'])
2

创建的匹配这个 YAML 文件的数据结构超过了一层嵌套。如果你想得到第二首诗歌的题目,使用 dict/list/dict 的引用:

>>> data['poems'][1]['title']
'Canadian Charms'

配置文件

许多程序提供多种选项和设置。动态的设置可以通过传入程序参数,但是持久的参数需要保存下来。因此,尽管你急着想快速定义自己的配置文件格式,但是最好不要这样做。你定义的格式可能既粗糙也没有节省时间。你需要同时维护写入配置文件的程序以及读取配置文件的程序(有时被称为解析程序)。其实,在程序中配置文件可以有许多好的选择,包括之前几节中提到的格式。

我们使用标准 configparser 模块处理 Windows 风格的初始化 .ini 文件。这些文件都包含 key = value 的定义,以下是一个简单的配置文件 settings.cfg 例子:

[english]
greeting = Hello

[french]
greeting = Bonjour

[files]
home = /usr/local
# 简单的插入:
bin = %(home)s/bin

下面的代码是把配置文件读入到 Python 数据结构:

>>> import configparser
>>> cfg = configparser.ConfigParser()
>>> cfg.read('settings.cfg')
['settings.cfg']
>>> cfg
<configparser.ConfigParser object at 0x1006be4d0>
>>> cfg['french']
<Section: french>
>>> cfg['french']['greeting']
'Bonjour'
>>> cfg['files']['bin']
'/usr/local/bin'

如果你需要两层以上的嵌套结构,使用 YAML 或者 JSON。

使用pickle序列化

存储数据结构到一个文件中也称为序列化(serializing)。像 JSON 这样的格式需要定制的序列化数据的转换器。Python 提供了 pickle 模块以特殊的二进制格式保存和恢复数据对象。

还记得 JSON 解析 datetime 对象时出现问题吗?但对于 pickle 就不存在问题:

>>> import pickle
>>> import datetime
>>> now1 = datetime.datetime.utcnow()
>>> pickled = pickle.dumps(now1)
>>> now2 = pickle.loads(pickled)
>>> now1
datetime.datetime(2014, 6, 22, 23, 24, 19, 195722)
>>> now2
datetime.datetime(2014, 6, 22, 23, 24, 19, 195722)

pickle 同样也适用于自己定义的类和对象。现在,我们定义一个简单的类 Tiny,当其对象强制转换为字符串时会返回 'tiny':

>>> import pickle
>>> class Tiny():
...     def __str__(self):
...        return 'tiny'
...
>>> obj1 = Tiny()
>>> obj1
<__main__.Tiny object at 0x10076ed10>
>>> str(obj1)
'tiny'
>>> pickled = pickle.dumps(obj1)
>>> pickled
b'\x80\x03c__main__\nTiny\nq\x00)\x81q\x01.'
>>> obj2 = pickle.loads(pickled)
>>> obj2
<__main__.Tiny object at 0x10076e550>
>>> str(obj2)
'tiny'

pickled 是从对象 obj1 转换来的序列化二进制字符串。然后再把字符串还原成对象 obj1 的副本 obj2。使用函数 dump() 序列化数据到文件,而函数 load() 用作反序列化。

参考文档