文章
合集Python 标准库第 4 / 8 篇

Python 标准库 Day 2 回顾:怎么读写

  1. 读出来的"外层"是什么类型(顶层容器)
  2. 里面的值是什么类型(细节)
  3. 写入时要传什么类型
  4. 写之前要不要做类型处理

JSON

data = json.load(f)

1. 外层是什么:取决于 JSON 文件最外层结构

{"a": 1}        →  data 是 dict
[1, 2, 3]       →  data 是 list
"hello"         →  data 是 str
42              →  data 是 int

实际项目里 99% 是 dict 或 list。

2. 里面的值是什么类型:JSON 标准类型自动映射回 Python

JSON → Python
─────────────
object  →  dict
array   →  list  
string  →  str
number  →  int 或 float(看有没有小数点)
true/false  →  bool
null    →  None

举例:


data = json.load(f)


type(data)            # dict
type(data["name"])    # str
type(data["age"])     # int           ← 自动是数字!
type(data["tags"])    # list
type(data["active"])  # bool

3. 写入要传什么:

json.dump(obj, f)

obj 必须是上面那张映射表里的类型组合:dict / list / str / int / float / bool / None 嵌套。

4. 要不要预处理:

如果你的数据全是基础类型 → 直接传,不用处理:

data = {"name": "Alice", "scores": [90, 85, 78]}
json.dump(data, f)   # OK

遇到这些类型必须先转换:

不支持的类型怎么转
datetimedt.isoformat()
→ str
Pathstr(path)
→ str
setlist(s)
→ list
tuple自动转 list(无需手动)
Decimalfloat()
或 str()
自定义类obj.__dict__
或自己写转换函数

CSV

reader = csv.DictReader(f)
rows = list(reader)

1. 外层是什么:list[dict](行的列表,每行一个 dict)

rows = [
    {"name": "Alice", "age": "25"},   # 第 1 行
    {"name": "Bob", "age": "30"},     # 第 2 行
]

2. 里面的值是什么类型:全部是 str!

type(rows[0])           # dict
type(rows[0]["name"])   # str
type(rows[0]["age"])    # str        ← 注意!不是 int

CSV 文件里没有类型信息,所以 csv 模块统一返回字符串。

3. 写入要传什么:

writer = csv.DictWriter(f, fieldnames=["name", "age"])
writer.writeheader()
writer.writerows(rows)   # rows 必须是 list[dict]

每个 dict 的 key 必须和 fieldnames 一致。

4. 要不要预处理:

可以传 int/float/bool——csv 模块会自动调用 str() 把它们变字符串写入:

writer.writerow({"name": "Alice", "age": 25, "score": 98.5})  # OK

但注意:写出去的文件里,25 和 "25" 长得一模一样:

name,age,score
Alice,25,98.5

下次读回来全是字符串,类型信息永久丢失。这是 CSV 的根本局限。

不能传:复杂对象(dict / list / datetime 等)。要先自己 str(...) 或 json.dumps(...) 转字符串。

INI

config = configparser.ConfigParser()
config.read(path, encoding="utf-8")

1. 外层是什么:ConfigParser 对象,不是普通 dict!但用法像两层嵌套 dict:

config["database"]                # 是 SectionProxy 对象(像 dict)
config["database"]["port"]        # 是 str

可以用 dict(config[...]) 转成普通 dict:

section = dict(config["database"])    # {"host": "localhost", "port": "5432"}

2. 里面的值是什么类型:全部是 str!和 CSV 一样

type(config["database"]["port"])   # str  ← '5432' 不是 5432

要拿其他类型必须用专门的 getter:

config.getint("database", "port")       # 5432 (int)
config.getfloat("database", "ratio")    # 0.8 (float)  
config.getboolean("database", "debug")  # True (bool)

3. 写入要传什么:

config["section"]["key"] = "value"    # 必须是字符串!
config.write(f)

4. 要不要预处理:

必须先转成字符串:

config["server"]["port"] = 8080            # TypeError
config["server"]["port"] = str(8080)        # OK
config["server"]["debug"] = "true"          # OK

Pickle

data = pickle.load(f)

1. 外层是什么:完全取决于你存进去的是什么

pickle.dump([1, 2, 3], f)         # 取出来是 list
pickle.dump({"a": 1}, f)          # 取出来是 dict
pickle.dump(my_dataframe, f)       # 取出来是 DataFrame
pickle.dump(some_function, f)      # 取出来是函数对象

pickle 是"原样存原样取",不做任何转换。

2. 里面的值是什么类型:和你存进去时一模一样,包括嵌套


pickle.dump({"date": datetime.now(), "items": {1, 2, 3}}, f)


data = pickle.load(f)
type(data["date"])   # datetime    ← 没变!
type(data["items"])  # set          ← 也没变!

这是 pickle 最大的优势:类型完全保真。

3. 写入要传什么:

pickle.dump(obj, f)

obj 几乎可以是任何 Python 对象——dict、list、datetime、set、自定义类、函数引用、嵌套结构……

4. 要不要预处理:

几乎都不用!这是 pickle 相对 JSON 的最大便利。

极少数不可 pickle 的:

  • 文件对象(open() 返回的)
  • 数据库连接、锁、socket
  • lambda 表达式(命名函数 OK)
  • 局部函数(def 在另一个函数内部定义的)

安全提醒:永远别 pickle.load 不可信来源的数据。

TOML

data = tomllib.load(f)   # 注意 f 必须是二进制模式打开

1. 外层是什么:永远是 dict

type(data)   # dict

TOML 文件的整体永远是「键值对集合」,对应 Python 的 dict。

2. 里面的值是什么类型:TOML 有完整的类型系统,自动映射回 Python


title = "My Blog"            # str
port = 8080                  # int
ratio = 0.8                  # float
debug = true                 # bool
hosts = ["a", "b"]           # list
created = 2024-01-15         # datetime.date
[database]                   # dict(嵌套)
host = "localhost"

读出来:

data = tomllib.load(f)
type(data["title"])    # str
type(data["port"])     # int        ← 自动是数字
type(data["debug"])    # bool       ← 自动是 bool
type(data["hosts"])    # list
type(data["created"])  # datetime.date  ← 甚至日期都自动识别
type(data["database"]) # dict

3. 写入要传什么:标准库 tomllib不支持写入!要写得装第三方包 tomli-w。


import tomli_w
with open(path, "wb") as f:
    tomli_w.dump(data, f)

4. 要不要预处理:用 tomli-w 时,传基础类型 + datetime 都行,比 JSON 还宽松。

一张总结表

格式load 出的外层里面值的类型写入要传写前要处理吗
JSONdict 或 list自动映射(int/float/bool/None/str/list/dict)基础类型嵌套datetime/set/Path/自定义类 → 要转
CSVlist[dict]全是 strlist[dict]int/float 能直接传(自动转 str),复杂对象要转
INIConfigParser 对象全是 strstr任何非 str 都要 **str()**
转
Pickle你存什么取什么完全保留原类型任何 Python 对象几乎不用
TOMLdict类型完整(含 datetime)标准库不能写(写需第三方库)

实战决策树

写函数时这样想:

Step 1:我读的是什么格式? → 查表「load 出的外层」
Step 2:我要拿的具体值是什么类型? → 查表「里面值的类型」
Step 3:CSV 和 INI 的值都是 str,我需要数字吗? → 是的话 int() / float() 转
Step 4:我要写的格式是什么? → 查表「写入要传」
Step 5:我手上的对象符合要求吗? → 不符合就先处理(看「写前要处理吗」一栏)

Maybe Useful 旁注

一个心智简化:把这五个格式分成两类:

  • "哑"格式(CSV / INI):文件里只有字符串。读出来全是 str,写入时类型信息丢失。
  • "聪明"格式(JSON / TOML / Pickle):保留类型。读出来是 int 就是 int,是 dict 就是 dict。

"哑"格式适合给非程序员(Excel 用户、运维)看;"聪明"格式适合程序之间交换。

类型保真度排名(从高到低):

Pickle > TOML > JSON > CSV ≈ INI

Pickle 几乎全保真,TOML 有 datetime,JSON 没有 datetime/set,CSV/INI 啥都没有全是 str。

工程口诀:「用 CSV 和 INI 时,永远假设拿到的是字符串」。这条记住了能避免 80% 的类型 bug。