Python 标准库 Day 2 回顾:怎么读写
- 读出来的"外层"是什么类型(顶层容器)
- 里面的值是什么类型(细节)
- 写入时要传什么类型
- 写之前要不要做类型处理
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
遇到这些类型必须先转换:
| 不支持的类型 | 怎么转 |
|---|---|
datetime | dt.isoformat()→ str |
Path | str(path)→ str |
set | list(s)→ list |
tuple | 自动转 list(无需手动) |
Decimal | float()或 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 出的外层 | 里面值的类型 | 写入要传 | 写前要处理吗 |
|---|---|---|---|---|
| JSON | dict 或 list | 自动映射(int/float/bool/None/str/list/dict) | 基础类型嵌套 | datetime/set/Path/自定义类 → 要转 |
| CSV | list[dict] | 全是 str | list[dict] | int/float 能直接传(自动转 str),复杂对象要转 |
| INI | ConfigParser 对象 | 全是 str | str | 任何非 str 都要 **str()**转 |
| Pickle | 你存什么取什么 | 完全保留原类型 | 任何 Python 对象 | 几乎不用 |
| TOML | dict | 类型完整(含 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。