Json-os库常用速查

搞懂你代码里的 osjson —— 从记忆系统的真实调用讲起

0. 先记住一句话

  • os = 和"操作系统 / 文件路径"打交道的工具箱。你用它定位文件在哪、拼出路径
  • json = 把 Python 的 dict / list 这类数据,变成字符串(存起来)从字符串还原回来(读出来) 的工具。

你记忆系统里它们的关系:os 负责"把库文件放在哪个绝对位置",json 负责"把要存的数据变成能写进文件/数据库的文本"。两个配合,记忆才能"存得下、读得回"。


一、os.path:和路径打交道

你的 paths.py 第一行就是它:

1
2
3
4
5
import os
BASE = os.path.dirname(os.path.abspath(__file__))
MEM_STORE   = os.path.join(BASE, "mem_store")
PROFILE_DB  = os.path.join(BASE, "memory.db")
FACTS_JSON  = os.path.join(BASE, "facts.json")

下面把这一行拆成三块讲。

1.1 __file__ —— “我这个文件自己叫啥、在哪”

每个 .py 文件运行时,Python 都会给它一个隐藏变量 __file__,内容是这个脚本的路径

  • 如果你在 C:\x\memory\ 里跑 python paths.py,那 __file__ 大致就是 C:\x\memory\paths.py
  • 但有时候它可能是相对路径(取决于你怎么启动),所以不能裸用。

1.2 os.path.abspath(__file__) —— 变成"从盘符算起的绝对路径"

1
2
os.path.abspath(__file__)
# 例:C:\Users\Think\Desktop\AgentLearn\memory\paths.py

作用:无论你从哪个目录启动程序,都把它矫正成完整的绝对路径。这样后面算出来的位置就不会随"当前目录(cwd)“漂移——这正是你记忆库"重启后还在原地"的关键。

1.3 os.path.dirname(...) —— 只拿走"目录"部分

1
2
os.path.dirname("C:\...\memory\paths.py")
# 得到:C:\Users\Think\Desktop\AgentLearn\memory

它把路径最后一段(文件名)砍掉,只留"文件夹”。于是:

1
2
BASE = os.path.dirname(os.path.abspath(__file__))
# BASE = C:\Users\Think\Desktop\AgentLearn\memory   ← 本文件所在目录

记忆里的作用:所有库(Chroma 文件夹、SQLite 文件)都建在 BASE 旁边,换目录运行也指向同一处,不会"找不到记忆"。

1.4 os.path.join(a, b) —— 安全地拼路径(别手写斜杠!)

1
2
3
os.path.join(BASE, "mem_store")
# Windows → C:\...\memory\mem_store
# Linux   → /home/.../memory/mem_store

为什么不用 BASE + "/mem_store"?因为 Windows 用 \、Linux/Mac 用 /,手写斜杠在不同系统会出错。os.path.join 自动用当前系统的正确分隔符,跨平台安全。

💡 你项目里所有的落盘路径都该用 os.path.join 拼,不要字符串硬拼。

1.5 os.path.exists(path) —— “这个路径在不在?”

来自你 local_store 示意里:

1
2
3
import json, os
def load_facts(path=FACTS_JSON):
    return json.load(open(path, encoding="utf-8")) if os.path.exists(path) else {}
1
os.path.exists(path)   # 文件/文件夹存在 → True;不存在 → False

作用:第一次运行时 facts.json 还没建,os.path.exists 返回 False,就直接返回空字典 {},避免去读一个不存在的文件而报错。


二、json:把数据变成"能存起来的文本"

SQLite 的字段是文本,文件也想存结构化的 Python 对象dict / list)。但文本只能存字符串,不能直接塞 dictjson 就是干这个转换的。

你的 profile_store.py

1
2
3
4
5
import sqlite3, json
...
self.con.execute("INSERT OR REPLACE INTO profile VALUES(?,?)", (k, json.dumps(v)))
...
return json.loads(row[0]) if row else None

local_store 示意:

1
2
json.load(open(path, encoding="utf-8"))
json.dump(d, open(path, "w", encoding="utf-8"), ensure_ascii=False, indent=2)

2.1 json.dumps(obj) —— Python 对象 → JSON 字符串

1
2
3
4
import json
v = {"age": 18, "like": "DeepSeek"}
s = json.dumps(v)
# s 现在是字符串:'{"age": 18, "like": "DeepSeek"}'

在记忆里的作用ProfileStore.set_profile(k, v) 要把用户的画像 v(通常是 dict)写进 SQLite。但 SQLite 字段是文本,所以先用 json.dumps(v) 把它变成字符串,再存进去。

2.2 json.loads(s) —— JSON 字符串 → Python 对象

1
2
json.loads('{"age": 18, "like": "DeepSeek"}')
# 还原成 dict:{"age": 18, "like": "DeepSeek"}

在记忆里的作用get_profile(k) 从 SQLite 读出来的是字符串(row[0]),用 json.loads(row[0]) 还原成原来的 dict,调用方才能当对象用。

2.3 json.dump(obj, f) / json.load(f) —— 直接读写整个文件

和上面那对很像,但这是直接操作文件对象

1
2
3
4
5
# 写:把 dict d 直接写进 path 这个文件
json.dump(d, open(path, "w", encoding="utf-8"), ensure_ascii=False, indent=2)

# 读:从 path 这个文件直接读回 dict
d = json.load(open(path, encoding="utf-8"))
方法吃进来的是吐出去的是记忆里谁用
json.dumps(obj)Python 对象字符串profile_store 写 SQLite 前
json.loads(s)字符串Python 对象profile_store 读 SQLite 后
json.dump(obj, f)对象 + 文件对象无(写进文件)local_storefacts.json
json.load(f)文件对象Python 对象local_storefacts.json

记法:s 的那对(dumps/loads)处理"字符串";不带 s 的那对(dump/load)处理"文件"

2.4 两个关键参数(你代码里已经用上了)

1
json.dump(d, f, ensure_ascii=False, indent=2)
  • ensure_ascii=False:默认 True 时,中文会被转成 \u4e2d\u6587 这种乱码。False 才能正常显示"用户喜欢 DeepSeek"。存中文必加。
  • indent=2:让 JSON 文件带缩进、换行,人眼可读。不加就是一整行挤在一起。

三、把 os + json 串起来看你的记忆系统

整条链路其实就三步:

  1. os 定位保险柜 paths.pyos.path.dirname(os.path.abspath(__file__)) + os.path.join 算出 MEM_STORE / PROFILE_DB / FACTS_JSON 三个绝对路径

  2. json 把数据打包

    • 存用户画像:json.dumps(v) 把 dict 变字符串 → 写进 SQLite(PROFILE_DB)。
    • 存简单事实:json.dump(d, f, ensure_ascii=False, indent=2) 直接写 facts.json
  3. json 再把数据拆包

    • 读画像:json.loads(row[0]) 把 SQLite 里的字符串还原成 dict。
    • 读事实:json.load(f) 直接读回 facts.json,并用 os.path.exists 判断文件是否存在。

一句话:os 管"放在哪",json 管"怎么变成能存的文本"。


四、新手最常踩的坑(重点看)

坑 1:json.dumps 默认中文变乱码

1
2
json.dumps({"like": "深度求索"})        # '{"like": "\\u6df1\\u5ea6\\u6c42\\u7d22"}'  ← 看不懂
json.dumps({"like": "深度求索"}, ensure_ascii=False)  # '{"like": "深度求索"}'  ← 正确

坑 2:loadsload 搞混

1
2
json.loads(open("a.json"))   # ❌ 报错!loads 只吃字符串,不吃文件对象
json.load(open("a.json"))    # ✅ load 才吃文件对象

坑 3:手写路径分隔符

1
2
BASE + "/mem_store"     # ❌ Windows 上会出错
os.path.join(BASE, "mem_store")   # ✅ 跨平台安全

坑 4:文件打开不关(你代码里的隐藏小问题)

你现在的 json.load(open(path)) / open(path, "w") 没有关文件。学习期不影响,但规范写法用 with,出作用域自动关:

1
2
3
4
5
6
# 更稳的写法
with open(path, "w", encoding="utf-8") as f:
    json.dump(d, f, ensure_ascii=False, indent=2)

with open(path, encoding="utf-8") as f:
    d = json.load(f)

五、速查卡片(复习直接看这)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# ===== os.path:定位与拼路径 =====
__file__                       # 当前脚本路径(可能相对)
os.path.abspath(__file__)      # → 绝对路径(从盘符算起)
os.path.dirname(p)             # 拿走目录部分,去掉文件名
os.path.join(a, b)            # 安全拼路径(跨平台)
os.path.exists(p)             # 路径在不在(bool)

# ===== json:对象 ↔ 文本 =====
json.dumps(obj, ensure_ascii=False, indent=2)  # 对象 → 字符串(ensure_ascii=False 保中文)
json.loads(s)                  # 字符串 → 对象
json.dump(obj, f, ensure_ascii=False, indent=2) # 对象 → 写进文件 f
json.load(f)                   # 从文件 f 读回对象
# 口诀:带 s 处理"字符串",不带 s 处理"文件"
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计