文件

Python 中对文件处理的方法
作者

HPDell

1 文件

理论上讲,文件是对任何读写设备的抽象。 读写设备有很多,主要就是硬盘和网卡。

1.1 文件类型

说到类型,主要指的是两种:二进制文件和文本文件。

  • 二进制文件:由二进制数据构成的文件,比如图片、程序、压缩包等。通常是难以阅读的,需要一定的程序进行读取。
  • 文本文件:由字符构成的文件,只要使用文本编辑器打开就可以直接阅读。
    • 字符包括可见字符和不可见字符,回车、换行、制表都是不可见字符。字母、数字和特殊符号都是可见字符。
    • 所有字符(包括中文)都是由一定的二进制码记录的,这种字符与二进制的对应方式称为“编码”。常见的编码方式有 ASCII、UTF-8、GB2312 等。

1.2 文件格式

不论是二进制文件还是文本文件,都有一定的格式。通常情况下,文件名的最后一个 . 后面的字符表示其格式,这部分被称为“扩展名”。

  • 二进制文件:JPEG、ZIP、EXE 等。二进制数据按照一定方式排列,才能正确执行。
  • 文本文件:
    • TXT:纯文本文件,没有什么固定的格式,只表示一些文本。
    • CSV:逗号分隔值,每行表示一个数据,每个数据由很多字段组成,字段之间由逗号分隔。
    • JSON:一般用于表示字典数据。

文件格式和扩展名并不是一回事
  • 文件格式是数据组织的方式,通常由一定的标准指定。而文件扩展名只是文件名的一部分,用来表示文件格式。
  • 通常情况下扩展名和格式要一一对应,文件才能被正确读取。但是如果扩展名不对,只要格式是正确的,也可以用相应的程序打开。比如把图片的扩展名改成 .txt 一样可以用 PS 打开。
  • 扩展名可以任意修改,格式并不会随之变化,修改格式必须要通过一定的程序进行转换。 比如可以将 JSON 文件的扩展名写成 .txt 并不会影响文件的格式; JPG 图片的后缀修改成 .ai 并不代表它就变成了 Adobe Illustrator 文件。
  • 一个文件格式也可有多个扩展名表示,比如 JPEG 图片的扩展名可以是 .jpg .jpeg
  • 也可以根据自己的需要设定一些格式,但通常情况下用已经定义好的格式比较方便。

1.3 文件读写

几乎任何文件读写操作都要遵循以下几个步骤:

  • 打开文件:这个过程是获取文件的读写权,只有拥有读写权时才能读取文件。
    • 每个文件的读写权在同一时间只能有一个程序拥有。
    • 读写权有三种,读、写和追加,分别用 r w a 表示。
  • 操作文件:根据申请的读写权对文件进行操作。
  • 关闭文件:这一步非常重要。只有关闭了文件之后文件才能被其他程序使用,否则其他程序只有等当前程序退出了。通常操作完文件后直接关闭文件。

2 纯文本文件

2.1 打开并读取

Python 中自带纯文本文件的读写函数 open() ,用于打开文件。

file = open("./assets/demo.txt")
file.close()

然而,这样就需要手动关闭文件。所以通常不这样做,而是使用 with 关键字让 Python 自动关闭。

with open("./assets/demo.txt") as file:
    for row in file:
        print(row.strip())
title,year
与凤行,2024
风吹半夏,2022
谁是凶手,2021
楚乔传,2017
花千骨,2015

2.2 写入文件

print 就可以用来写入文件,参数 file 就可以指定输出位置。如果不指定,就是标准输出,一般情况下是控制台。

with open("./assets/out.txt", mode="w") as fout:
    print("赵丽颖参演过的电视剧:\n", file=fout)
    with open("./assets/demo.txt") as fin:
        for i, row in enumerate(fin):
            if i == 0:
                continue
            title, year = row.strip().split(",")
            print(f"- 《{title}》({year}年)", file=fout)

生成的文件如下:

赵丽颖参演过的电视剧:

- 《与凤行》(2024年)
- 《风吹半夏》(2022年)
- 《谁是凶手》(2021年)
- 《楚乔传》(2017年)
- 《花千骨》(2015年)

3 CSV 格式

3.1 文件格式

其实上述文件就已经是逗号分隔值文件,只是我们是按照纯文本文件的方式读取的。 Python 提供了一个读写 CSV 文件的包 csv

CSV 文件格式

CSV 格式没有明文规定的规范,但是有一定的通用规范。大致上:

  • 第一行是表头,用逗号分割每个列名(也称字段名)
  • 后续每行代表一条记录,各个列的属性以逗号分隔,与列名对应
  • 如果某个字段包含 , (分隔符),字段值用 "" 包裹

csv 提供了两种读取数据的方法,一种将每行解析为列表,另一种将每行解析为字典。

3.2 文件读取

不论是哪种读取方法,都需要打开文件并创建一个“读取器”。

import csv
with open("./assets/demo.txt", newline='') as fin:
    reader = csv.reader(fin)
    for row in reader:
        print(f"- 《{row[0]}》({row[1]}年)")
with open("./assets/demo.txt", newline='') as fin:
    reader = csv.DictReader(fin)
    for row in reader:
        print("* 《{title}》({year}年)".format_map(row))
- 《title》(year年)
- 《与凤行》(2024年)
- 《风吹半夏》(2022年)
- 《谁是凶手》(2021年)
- 《楚乔传》(2017年)
- 《花千骨》(2015年)
* 《与凤行》(2024年)
* 《风吹半夏》(2022年)
* 《谁是凶手》(2021年)
* 《楚乔传》(2017年)
* 《花千骨》(2015年)

3.3 文件写入

写文件的方法也是类似的,需要创建一个写入器。这里就以字典式为例。

demo_data = {
    "title": ["与凤行", "风吹半夏"],
    "year": [2024, 2022]
}
import csv
with open("./assets/demo_dict_writer_out.csv", mode="w", newline="") as fout:
    writer = csv.DictWriter(fout, demo_data.keys())
    writer.writeheader()
    for row in zip(*demo_data.values()):
        writer.writerow({
            "title": row[0],
            "year": row[1]
        })

3.4 读写器的选择

  • 如果文件描述的对象类似于字典,那么字典式读写器更方便。类似于字典的对象的特点是不同字段之间性质差别很大,但字段本身一一对应。
  • 如果文件没有表头,而且自定义表头是可行的,那字典式读写器还是要更方便一些。
  • 如果文件描述的不同对象有不同的字段,那么字典式读写器已经不再适用了。
  • 如果文件描述的对象本身就类似于列表,比如一个时间序列或矩阵,那么列表式读写器更方便。

3.5 更高级的工具:Pandas

Python 自带的 csv 包只提供了比较基础的操作 CSV 文件的功能,主要是文本处理的功能。 但是第三方的 Pandas 包提供了更强大的功能,包括解析、筛选、变换、聚合等。

import pandas as pd
d = pd.read_csv("./assets/demo.txt")
d.head(2)
title year
0 与凤行 2024
1 风吹半夏 2022
d['year'].min()
2015
d['actor'] = "赵丽颖"
d.head(2)
title year actor
0 与凤行 2024 赵丽颖
1 风吹半夏 2022 赵丽颖
d.count()
title    5
year     5
actor    5
dtype: int64

4 JSON 格式

4.1 文件格式

JSON 全称是“JavaScript 对象表示法”,但往往与 Python 原生类型对应(虽然不是一一对应)。

{
    "actor": "赵丽颖",
    "tv": [
        {"title": "与凤行", "year": 2024, "collabrators": ["林更新"]},
        {"title": "风吹半夏", "year": 2022, "collabrators": ["欧豪", "李光洁"]},
        {"title": "谁是凶手", "year": 2021, "collabrators": ["肖央", "董子健"]},
        {"title": "楚乔传", "year": 2017, "collabrators": ["林更新"]},
        {"title": "花千骨", "year": 2015, "collabrators": ["霍建华"]}
    ]
}

JSON 格式在网页和网络请求中的使用非常普遍,几乎所有现代网络编程接口(API)都是以 JSON 格式传递数据的。 因此该格式非常重要。

4.2 读取和解析

在 CSV 文件和纯文本文件中,我们主要使用的是读取器,通过不断读取文件内容执行操作。 但 JSON 格式的文件通常不是一行一行地读取的,而是整个进行解析的。 因此,需要首先读取文件全部内容,再按照 JSON 格式解析。 可见,我们主要需要使用 JSON 解析器(Parser),而至于解析的对象是文件还是字符串就不是很重要了。

反之也一样,通常 CSV 文件是一行一行写入的,但 JSON 文件需要整个写入,否则格式就出错了。 因此需要生成全部文件内容,再全部写到文件中,这里需要的就是 JSON 格式字符串的“生成器”,也叫“序列化器”(Serializer)。

Python 提供了包 json 用于解析和生成 JSON 字符日,但提供了直接操作文件的功能。

4.3 解析

使用 load() 函数直接解析 JSON 文件。

import json
with open("./assets/demo_json.json") as fin:
    data = json.load(fin)
    print(data)
{'actor': '赵丽颖', 'tv': [{'title': '与凤行', 'year': 2024, 'collabrators': ['林更新']}, {'title': '风吹半夏', 'year': 2022, 'collabrators': ['欧豪', '李光洁']}, {'title': '谁是凶手', 'year': 2021, 'collabrators': ['肖央', '董子健']}, {'title': '楚乔传', 'year': 2017, 'collabrators': ['林更新']}, {'title': '花千骨', 'year': 2015, 'collabrators': ['霍建华']}]}

也可以先读取文件为字符串再使用 loads() 解析。

from pathlib import Path
json.loads(Path("./assets/demo_json.json").read_text())
{'actor': '赵丽颖',
 'tv': [{'title': '与凤行', 'year': 2024, 'collabrators': ['林更新']},
  {'title': '风吹半夏', 'year': 2022, 'collabrators': ['欧豪', '李光洁']},
  {'title': '谁是凶手', 'year': 2021, 'collabrators': ['肖央', '董子健']},
  {'title': '楚乔传', 'year': 2017, 'collabrators': ['林更新']},
  {'title': '花千骨', 'year': 2015, 'collabrators': ['霍建华']}]}

4.4 生成

dump()dumps() 分别用于生成 JSON 文件和字符串。

json.dumps(demo_data, ensure_ascii=False)
'{"title": ["与凤行", "风吹半夏"], "year": [2024, 2022]}'
with open("./assets/demo_json_out.json", mode="w") as fout:
    json.dump(demo_data, fout, ensure_ascii=False)

也可以生成具有一定缩进格式的字符串

print(json.dumps(demo_data, ensure_ascii=False, indent=4))
{
    "title": [
        "与凤行",
        "风吹半夏"
    ],
    "year": [
        2024,
        2022
    ]
}

5 下节

注释和注解

  • 注释
  • 类型注解