目录
- 简介
- 快速开始
- 进阶用法
-
- 查询操作
- 数据处理
-
- [Inserting data(插入数据)](#Inserting data(插入数据))
- [Updating data(更新数据)](#Updating data(更新数据))
- 数据访问与修改
-
- [Upserting data(插入或更新)](#Upserting data(插入或更新))
- [Retrieving data(数据获取)](#Retrieving data(数据获取))
-
- [1. 获取文档数量](#1. 获取文档数量)
- [2. 查询数据(search)](#2. 查询数据(search))
- [3. 获取单条数据(get)](#3. 获取单条数据(get))
- [4. 判断是否存在(contains)](#4. 判断是否存在(contains))
- [5. 统计匹配数量(count)](#5. 统计匹配数量(count))
- 速查表
- [使用文档 ID](#使用文档 ID)
- 表
- [Storage & Middleware(存储与中间件)](#Storage & Middleware(存储与中间件))
-
- [Storage Types(存储类型)](#Storage Types(存储类型))
- Middleware(中间件)
- 类型检查
- [扩展 TinyDB](#扩展 TinyDB)
- 结尾(Conclusion)
简介
为什么使用 TinyDB?
tiny(轻量)
目前源码大约只有 1800 行(其中约 40% 是文档),以及 1600 行测试代码。
面向文档(document oriented)
类似 MongoDB,你可以在 TinyDB 中存储任意文档(以 dict 形式表示)。
以开发体验为核心(optimized for your happiness)
TinyDB 的设计目标是简单且易用,提供干净、直观的 API,让使用过程更加轻松愉快。
纯 Python 实现(written in pure Python)
TinyDB 不需要外部服务(例如 PyMongo 依赖 MongoDB 服务),也不依赖 PyPI 上的其他包。
兼容 Python 3.5+ 与 PyPy
可以运行在所有现代 Python 版本以及 PyPy 上。
高度可扩展(powerfully extensible)
你可以通过编写自定义存储(storage)或使用中间件(middleware)来轻松扩展 TinyDB 的功能或修改其行为。
100% 测试覆盖率
无需多言。
如果你需要一个无需复杂配置、开箱即用、API 简洁的轻量数据库,TinyDB 可能是一个不错的选择。
但,如果你有以下需求,那么 TinyDB 并不适合你:
- 多进程或多线程访问(例如在 Flask 场景中)
- 表索引(index)
- HTTP 服务接口
- 表之间的关系管理(类似关系型数据库)
- ACID 事务保证
或者:
👉 你对性能要求非常高,需要一个高速数据库
快速开始
只需要运行:
python
pip install tinydb
在深入细节之前,我们先快速了解一下 TinyDB 的基本使用方式。
1. 创建数据库
python
from tinydb import TinyDB, Query
db = TinyDB('db.json')
现在你已经创建了一个 TinyDB 数据库,它会将数据存储在 db.json 文件中。
2. 插入数据
TinyDB 以 Python 的 dict 作为数据存储格式:
python
db.insert({'type': 'apple', 'count': 7})
db.insert({'type': 'peach', 'count': 3})
💡 注意 :
insert方法会返回插入文档的 ID(Document ID)。
3. 查询全部数据
python
db.all()
输出:
python
[
{'count': 7, 'type': 'apple'},
{'count': 3, 'type': 'peach'}
]
4. 遍历数据
python
for item in db:
print(item)
输出:
python
{'count': 7, 'type': 'apple'}
{'count': 3, 'type': 'peach'}
5. 条件查询
python
Fruit = Query()
db.search(Fruit.type == 'peach')
# [{'count': 3, 'type': 'peach'}]
db.search(Fruit.count > 5)
# [{'count': 7, 'type': 'apple'}]
6. 更新数据
python
db.update({'count': 10}, Fruit.type == 'apple')
db.all()
结果:
python
[
{'count': 10, 'type': 'apple'},
{'count': 3, 'type': 'peach'}
]
7. 删除数据
python
db.remove(Fruit.count < 5)
db.all()
结果:
python
[
{'count': 10, 'type': 'apple'}
]
8. 清空数据库
python
db.truncate()
db.all()
# []
速查表
在继续深入之前,我们先总结TinyDB 的基础用法:
| 分类 | 用法 | 说明 |
|---|---|---|
| 插入(Inserting) | db.insert(...) |
插入一条文档 |
| 查询(Getting Data) | db.all() |
获取所有文档 |
iter(db) |
遍历所有文档 | |
db.search(query) |
获取符合条件的文档列表 | |
| 更新(Updating) | db.update(fields, query) |
更新所有匹配条件的文档 |
| 删除(Removing) | db.remove(query) |
删除所有匹配条件的文档 |
db.truncate() |
删除所有文档(清空数据库) | |
| 查询构造(Querying) | Query() |
创建一个查询对象 |
Query().field == 2 |
匹配 field == 2 的文档(也支持 !=, >, >=, <, <=) |
TinyDB 的核心操作其实就围绕这四类:CRUD + Query 构造,没有额外抽象层,这也是它"简单"的本质。
注意事项
1. 查询限制
Query 只支持右侧为"字面量"的比较,例如:
python
Query().a == 1 # ✅ 支持
Query().a == Query().b # ❌ 不支持
如果需要字段之间比较,可以使用自定义函数:
python
db.search(lambda doc: doc.get('a') == doc.get('b'))
2. 安全性提醒
传入查询 API 的函数(例如 lambda 或 Query().map)是在当前进程中执行的:
👉 绝对不要使用来自用户输入的不可信代码
否则可能带来安全风险。
3. TinyDB查询本质
TinyDB 的查询看起来很像 ORM,但实际上是基于 Python 表达式构建的,这也决定了它在灵活性和限制之间的权衡。
TinyDB 默认情况下基本就是"全量扫描 + Python 条件过滤",而不是像 SQLite 那样做索引优化和查询计划。
像你写的:
python
db.search(Fruit.count > 5)
本质上会变成类似:
python
for doc in 所有数据:
if 条件表达式(doc):
收集结果
也就是说:
Query()构建的是一个可执行的条件对象- 最终是在 Python 层面对每条数据执行判断
- 没有查询优化器
- 没有索引(默认情况下)
为什么它看起来"像 ORM"?
因为写法是:
python
Fruit.count > 5
这种 DSL 很像 ORM(比如 Django ORM / SQLAlchemy)
但本质区别是:
| 对比点 | TinyDB | ORM(+ SQL数据库) |
|---|---|---|
| 执行位置 | Python 层 | 数据库引擎 |
| 查询方式 | 全表扫描 | 索引 + 执行计划 |
| 表达式 | Python 对象 | SQL AST |
| 性能 | O(n) | 可优化 |
正因为它是在 Python 层执行,所以你可以写:
python
db.search(lambda doc: doc['a'] == doc['b'])
甚至更复杂逻辑:
python
db.search(lambda doc: doc['count'] % 2 == 0 and len(doc['type']) > 3)
👉 这在 SQLite 里反而是麻烦甚至不可能直接表达的(需要写函数 / 扩展)。
带来的问题就是:
- 数据量一大 → 性能线性下降
- 并发 → 基本不支持
- 复杂查询 → 全靠你自己写逻辑
TinyDB 并不是"数据库在执行查询",而是"Python 在帮你筛数据"。
这让它极其灵活,但也天然放弃了索引、优化器和高性能。
它其实连"数据库"都算不上,更像一个可持久化的 dict + 查询引擎。
进阶用法
默认情况下,TinyDB 使用 Python 内置的 JSON 模块 将数据序列化并写入磁盘。
- 优点:简单、直观,适合基础数据类型
- 缺点:无法处理复杂类型(例如自定义类)
此外,在 Python 2 中读取数据时还会涉及字符串转 Unicode 的问题(历史遗留问题)。
👉 如果这些限制对你有影响,你可以:
- 自定义 Storage
- 使用更强大的序列化库(例如
pickle或PyYAML)(但通常会更慢)
💡 提示(Hint)
在同一份数据上打开多个 TinyDB 实例(例如使用 JSONStorage)可能会因为**查询缓存(query cache)**导致异常行为。
可以通过关闭 query cache 来避免这个问题。
查询操作
TinyDB 提供了两种主要的查询构造方式。
第一种方式类似 ORM 风格:
python
from tinydb import Query
User = Query()
db.search(User.name == 'John')
可以像访问属性一样访问字段,也支持嵌套字段:
python
db.search(User.birthday.year == 1990)
如果字段名不是合法的 Python 标识符,则需要使用字典方式访问:
python
db.search(User['country-code'] == 'foo')
此外,还可以在字段上应用转换函数:
python
from unidecode import unidecode
db.search(User.name.map(unidecode) == 'Jose')
第二种是更传统的写法:
python
from tinydb import where
db.search(where('field') == 'value')
它本质上只是 Query()['field'] 的简写形式,同样支持嵌套字段访问。
高级查询
在 Getting Started 中你已经了解了基础比较操作(==, <, >, ...)。在此基础上,TinyDB 还支持以下查询方式:
python
# 字段存在性判断:
db.search(User.name.exists())
# 正则匹配:
# 整个字段必须匹配正则:
db.search(User.name.matches('[aZ]*'))
# 忽略大小写匹配 'John':
import re
db.search(User.name.matches('John', flags=re.IGNORECASE))
# 字段中任意部分匹配正则:
db.search(User.name.search('b+'))
# 自定义测试函数:
test_func = lambda s: s == 'John'
db.search(User.name.test(test_func))
# 带参数的自定义测试:
def test_func(val, m, n):
return m <= val <= n
db.search(User.age.test(test_func, 0, 21))
db.search(User.age.test(test_func, 21, 99))
另一种常见场景是:当你有一个 dict,希望查找所有包含该结构的文档,这种方式称为 fragment 查询(片段匹配):
python
db.search(Query().fragment({'foo': True, 'bar': False}))
[{'foo': True, 'bar': False, 'foobar: 'yes!'}]
你也可以限定在某个字段上进行 fragment 匹配:
python
db.search(Query().field.fragment({'foo': True, 'bar': False}))
[{'field': {'foo': True, 'bar': False, 'foobar: 'yes!'}]
当字段是列表时,可以使用 any 和 all 方法。有两种使用方式:基于值列表或基于嵌套查询。先看第一种。
假设有如下数据:
python
db.insert({'name': 'user1', 'groups': ['user']})
db.insert({'name': 'user2', 'groups': ['admin', 'user']})
db.insert({'name': 'user3', 'groups': ['sudo', 'user']})
可以这样查询:
python
# 用户的 groups 至少包含 ['admin', 'sudo'] 中的一个
db.search(User.groups.any(['admin', 'sudo']))
[{'name': 'user2', 'groups': ['admin', 'user']},
{'name': 'user3', 'groups': ['sudo', 'user']}]
# 用户的 groups 必须同时包含 ['admin', 'user']
db.search(User.groups.all(['admin', 'user']))
[{'name': 'user2', 'groups': ['admin', 'user']}]
在更复杂的场景中,可以使用嵌套查询 来配合 any / all:
python
Group = Query()
Permission = Query()
groups = db.table('groups')
groups.insert({
'name': 'user',
'permissions': [{'type': 'read'}]})
groups.insert({
'name': 'sudo',
'permissions': [{'type': 'read'}, {'type': 'sudo'}]})
groups.insert({
'name': 'admin',
'permissions': [{'type': 'read'}, {'type': 'write'}, {'type': 'sudo'}]})
查询示例:
python
# group 至少有一个 'read' 权限
groups.search(Group.permissions.any(Permission.type == 'read'))
[{'name': 'user', 'permissions': [{'type': 'read'}]},
{'name': 'sudo', 'permissions': [{'type': 'read'}, {'type': 'sudo'}]},
{'name': 'admin', 'permissions':
[{'type': 'read'}, {'type': 'write'}, {'type': 'sudo'}]}]
# group 只包含 'read' 权限
groups.search(Group.permissions.all(Permission.type == 'read'))
[{'name': 'user', 'permissions': [{'type': 'read'}]}]
可以看到:
any:只要存在一个匹配即可all:必须全部匹配
相反的操作(判断字段值是否属于某个列表)可以使用 one_of:
python
db.search(User.name.one_of(['jane', 'john']))
查询组合
TinyDB 支持使用逻辑运算来组合查询:
python
# 取反(NOT)
db.search(~ (User.name == 'John'))
# 与(AND)
db.search((User.name == 'John') & (User.age <= 30))
# 或(OR)
db.search((User.name == 'John') | (User.name == 'Bob'))
⚠️ 注意
- 使用
&或|时,必须用括号包裹两边的表达式 - 使用
~时,也必须用括号包裹整个表达式
原因是:Python 中这些运算符的优先级高于比较运算符。例如:
python
~ User.name == 'John'
会被解析为:
python
(~User.name) == 'John'
而不是:
python
~(User.name == 'John')
可以使用空查询来动态构造条件:
python
Query().noop()
限制与注意事项
比较操作只支持右侧为字面量:
python
Query().a == Query().b # ❌ 不支持
如果需要字段之间比较,可以使用函数:
python
db.search(lambda doc: doc.get('a') == doc.get('b'))
⚠️ 安全提醒
所有传入查询 API 的函数(如 lambda、Query().map、Query().test):
👉 都是在当前进程中执行
👉 绝不能来自不可信输入
速查表
查询能力
| 操作 | 说明 |
|---|---|
Query().field.exists() |
匹配包含该字段的文档 |
Query().field.matches(regex) |
字段完全匹配正则 |
Query().field.search(regex) |
字段部分匹配正则 |
Query().field.test(func, *args) |
自定义函数判断 |
| `Query().field.all(query | list)` |
| `Query().field.any(query | list)` |
Query().field.one_of(list) |
字段值属于列表 |
逻辑运算
| 操作 | 说明 |
|---|---|
~ (query) |
逻辑非(NOT) |
(query1) & (query2) |
逻辑与(AND) |
| `(query1) | (query2)` |
数据处理
Inserting data(插入数据)
如前所述,可以使用 db.insert(...) 插入单条文档。如果需要插入多条数据,可以使用 db.insert_multiple(...):
python
db.insert_multiple([
{'name': 'John', 'age': 22},
{'name': 'John', 'age': 37}])
db.insert_multiple({'int': 1, 'value': i} for i in range(2))
在某些情况下,你可能希望手动指定文档 ID ,可以通过 Document 类实现:
python
db.insert(Document({'name': 'John', 'age': 22}, doc_id=12))
12
db.insert_multiple(...) 同样支持:
python
db.insert_multiple([
Document({'name': 'John', 'age': 22}, doc_id=12),
Document({'name': 'Jane', 'age': 24}, doc_id=14),
])
[12, 14]
⚠️ 注意
如果插入的
Document的 ID 已存在,会抛出ValueError。
Updating data(更新数据)
有时你希望更新数据库中的所有文档,这时可以省略查询条件:
python
db.update({'foo': 'bar'})
当你向 db.update(fields, query) 传入一个 dict 时,只能进行字段新增或覆盖 。
如果你需要更复杂的操作(例如删除字段或修改数值),可以传入一个函数:
python
from tinydb.operations import delete
db.update(delete('key1'), User.name == 'John')
这会从所有匹配的文档中删除 key1 字段。
TinyDB 内置了一些常用操作:
delete(key):删除字段increment(key):字段值递增decrement(key):字段值递减add(key, value):为字段增加值(字符串也支持)subtract(key, value):字段值减少set(key, value):设置字段值
当然,你也可以自定义操作:
python
def your_operation(your_arguments):
def transform(doc):
# do something with the document
# ...
return transform
db.update(your_operation(arguments), query)
如果需要一次执行多个更新操作,可以使用 update_multiple:
python
db.update_multiple([
({'int': 2}, where('char') == 'a'),
({'int': 4}, where('char') == 'b'),
])
也可以混合普通更新和操作函数:
python
db.update_multiple([
({'int': 2}, where('char') == 'a'),
({delete('int'), where('char') == 'b'),
])
TinyDB 的数据操作本质上是"对文档做函数式变换",而不是传统数据库的 SQL 更新语义。
数据访问与修改
Upserting data(插入或更新)
在某些场景下,你可能需要"更新 + 插入"二合一的操作:upsert(存在则更新,不存在则插入)。
该操作需要提供一个 document 和一个 query:
python
db.upsert({'name': 'John', 'logged-in': True}, User.name == 'John')
含义是:
- 如果存在
name == John的文档 → 更新这些文档 - 如果不存在 → 插入新文档
如果你希望使用 document ID 作为匹配条件 ,可以直接传入 Document:
python
db.upsert(Document({'name': 'John', 'logged-in': True}, doc_id=12))
Retrieving data(数据获取)
TinyDB 提供多种数据读取方式。
1. 获取文档数量
python
len(db)
输出示例:
3
💡 提示
这个值表示默认 table 中的文档数量(见 default table 相关说明)。
2. 查询数据(search)
最基础方式仍然是:
python
db.search(User.name == 'John')
但如果你只想要单条结果 ,可以使用 db.get(...)。
3. 获取单条数据(get)
传统写法:
python
try:
result = db.search(User.name == 'John')[0]
except IndexError:
pass
更简洁方式:
python
db.get(User.name == 'John')
返回结果:
{'name': 'John', 'age': 22}
如果没有匹配项:
None
⚠️ 注意
如果多个文档匹配 query,返回的结果可能是其中任意一个(不保证顺序)。
4. 判断是否存在(contains)
如果你只关心"是否存在",可以使用:
python
db.contains(User.name == 'John')
5. 统计匹配数量(count)
python
db.count(User.name == 'John')
输出示例:
2
速查表
| 操作 | 说明 |
|---|---|
db.insert_multiple(...) |
插入多条文档 |
db.update(operation, ...) |
使用操作函数更新文档 |
len(db) |
获取文档总数 |
db.get(query) |
获取一条匹配文档 |
db.contains(query) |
判断是否存在匹配文档 |
db.count(query) |
统计匹配文档数量 |
📝 Note
upsert是 TinyDB v3.6.0 引入的新特性。
使用文档 ID
在 TinyDB 内部,每一条插入的数据都会自动分配一个唯一 ID。
插入时返回 ID:
python
db.insert({'name': 'John', 'age': 22})
3
db.insert_multiple([{...}, {...}, {...}])
[4, 5, 6]
获取已存在文档的 ID:
可以通过 document.doc_id 获取:
python
el = db.get(User.name == 'John')
el.doc_id
3
python
el = db.all()[0]
el.doc_id
1
python
el = db.all()[-1]
el.doc_id
12
基于 ID 的操作:
以下方法都支持直接使用 ID:
updateremovecontainsget
python
db.update({'value': 2}, doc_ids=[1, 2])
db.contains(doc_id=1)
True
db.remove(doc_ids=[1, 2])
db.get(doc_id=3)
{...}
db.get(doc_ids=[1, 2])
[{...}, {...}]
👉 使用 doc_id / doc_ids 相比 Query():
- 速度更快
- 直接定位文档(无需扫描)
速查表
获取 ID
| 操作 | 说明 |
|---|---|
db.insert(...) |
返回插入文档 ID |
db.insert_multiple(...) |
返回多个 ID |
document.doc_id |
获取文档 ID |
基于 ID 的操作
| 操作 | 说明 |
|---|---|
db.get(doc_id=...) |
按 ID 获取文档 |
db.contains(doc_id=...) |
判断 ID 是否存在 |
db.update({...}, doc_ids=[...]) |
按 ID 更新 |
db.remove(doc_ids=[...]) |
按 ID 删除 |
表
TinyDB 支持多表结构,每个表的行为与数据库对象一致。
创建表:
python
table = db.table('table_name')
table.insert({'value': True})
table.all()
[{'value': True}]
遍历表:
python
for row in table:
print(row)
删除表
python
db.drop_table('table_name')
删除所有表
python
db.drop_tables()
获取所有表名
python
db.tables()
{'_default', 'table_name'}
Default Table(默认表)
TinyDB 使用 _default 作为默认表。
所有直接操作数据库对象的方法:
python
db.insert(...)
都会作用于默认表。
修改默认表名:
方式 1:仅当前实例
python
db = TinyDB(storage=SomeStorage)
db.default_table_name = 'my-default'
方式 2:全局修改
python
TinyDB.default_table_name = 'my-default'
Query Caching(查询缓存)
TinyDB 会缓存查询结果以提升性能:
- 相同查询不会重复读取存储层
- 只要数据库未变化,直接复用结果
设置缓存大小:
python
table = db.table('table_name', cache_size=30)
缓存配置说明:
cache_size = None→ 无限制缓存cache_size = 0→ 禁用缓存
注意事项:
- 不支持对同一表使用不同 cache 配置
- 第一次创建后,后续调用会复用相同配置
外部修改数据风险
缓存不会检测外部进程修改数据:
👉 可能导致读取到旧数据
解决方式:
python
db.clear_cache()
内存注意事项
当使用:
- 无限缓存
test()/ lambda 查询
可能导致:
👉 长期运行应用出现内存泄漏(因为函数引用未释放)
Storage & Middleware(存储与中间件)
Storage Types(存储类型)
TinyDB 内置两种存储方式:JSON 存储 和 内存存储。
默认情况下,数据会被写入 JSON 文件,因此需要指定文件路径:
python
from tinydb import TinyDB, where
db = TinyDB('path/to/db.json')
如果你不需要持久化,可以使用内存存储:
python
from tinydb.storages import MemoryStorage
db = TinyDB(storage=MemoryStorage)
除了 storage 参数外,其他参数都会被传递给底层 storage。
例如 JSON storage 支持传入 json.dumps(...) 的参数:
python
db = TinyDB('db.json', sort_keys=True, indent=4, separators=(',', ': '))
JSONStorage 会将 **kwargs 透传给 json.dumps。
⚠️ 不要传入用户可控的 callable 参数(如 default / cls)
因为它们会在写入时在进程内执行,存在安全风险。
可以全局修改默认 storage:
python
TinyDB.default_storage_class = MemoryStorage
有时你可能需要直接操作 storage:
python
db = TinyDB(storage=CachingMiddleware(MemoryStorage))
db.storage.flush()
Middleware(中间件)
Middleware 用于包装 storage,从而扩展其行为。
python
from tinydb.storages import JSONStorage
from tinydb.middlewares import CachingMiddleware
db = TinyDB('/path/to/db.json', storage=CachingMiddleware(JSONStorage))
可以多层组合:
python
db = TinyDB('/path/to/db.json',
storage=FirstMiddleware(SecondMiddleware(JSONStorage)))
TinyDB 提供的 CachingMiddleware 用于提升性能:
- 减少磁盘 IO
- 缓存读取结果
- 延迟写入磁盘
关闭数据库时,确保数据写入磁盘:
python
# 使用上下文管理器
with database as db:
# your operations
python
# 或手动关闭
db.close()
类型检查
TinyDB 提供类型注解,可用于 MyPy 静态检查。
但由于部分实现模式较复杂,MyPy 无法完全理解,因此提供了 plugin 支持。
启用 MyPy 插件:
在配置文件(mypy.ini 或 setup.cfg)中添加:
ini
[mypy]
plugins = tinydb.mypy_plugin
扩展 TinyDB
扩展 TinyDB 主要有四种方式:
- 自定义 storage
- 自定义 middleware
- 使用 hooks 和 overrides
- 继承 TinyDB / Table
编写自定义存储
TinyDB 默认提供两种存储:
- 内存存储
- JSON 文件存储
但你也可以实现自己的 storage,比如使用 PyYAML 实现 YAML 存储:
python
import yaml
class YAMLStorage(Storage):
def __init__(self, filename): # (1)
self.filename = filename
def read(self):
with open(self.filename) as handle:
try:
data = yaml.safe_load(handle.read()) # (2)
return data
except yaml.YAMLError:
return None # (3)
def write(self, data):
with open(self.filename, 'w+') as handle:
yaml.dump(data, handle)
def close(self): # (4)
pass
需要注意的几点:
(1) 构造函数参数来源
构造函数会接收创建 TinyDB 实例时传入的参数(除了 storage 本身)。
例如:
python
TinyDB('something', storage=YAMLStorage)
会把 'something' 传给 YAMLStorage。
⚠️ 如果你的 storage 接收可执行对象(如 callable),不要使用来自不可信输入的数据。
(2) 安全解析 YAML
这里使用:
python
yaml.safe_load
这是 PyYAML 推荐的安全方式,用于处理不可信数据源。
(3) 初始化返回 None
如果 storage 还没有初始化,TinyDB 期望返回:
python
None
以便框架执行内部初始化逻辑。
(4) close 方法
如果 storage 需要资源清理(比如关闭文件句柄),可以写在 close():
python
def close(self):
pass
关闭方式:
python
with TinyDB('db.yml', storage=YAMLStorage) as db:
# ...
或者:
python
db.close()
使用 YAML Storage,使用方式非常直接:
python
db = TinyDB('db.yml', storage=YAMLStorage)
# ...
编写自定义中间件
有时候你不需要重新实现一个 storage,而只是想修改已有 storage 的行为。
例如,这里我们实现一个 middleware,用来过滤掉空数据项。
在 TinyDB 中,middleware 本质上是对 storage 的一层包装,因此必须实现:
read()write(data)
同时可以通过 self.storage 访问底层 storage。
在开始实现之前,先看一下 middleware 处理的数据结构:
python
{
'_default': {
1: {'key': 'value'},
2: {'key': 'value'},
# other items
},
# other tables
}
可以看到数据结构是:
-
多 table
- 每个 table 包含多个 document
因此需要两层遍历:
- 遍历 table
- 遍历 document
Middleware 实现示例:
python
class RemoveEmptyItemsMiddleware(Middleware):
def __init__(self, storage_cls):
# Any middleware *has* to call the super constructor
# with storage_cls
super().__init__(storage_cls) # (1)
def read(self):
data = self.storage.read()
for table_name in data:
table_data = data[table_name]
for doc_id in table_data:
item = table_data[doc_id]
if item == {}:
del table_data[doc_id]
return data
def write(self, data):
for table_name in data:
table_data = data[table_name]
for doc_id in table_data:
item = table_data[doc_id]
if item == {}:
del table_data[doc_id]
self.storage.write(data)
def close(self):
self.storage.close()
关键点说明:
(1) 必须调用父类构造函数
python
super().__init__(storage_cls)
这一点是必须的,因为 middleware 需要知道底层使用的 storage 类型。
通过 middleware 包装 storage:
python
db = TinyDB(storage=RemoveEmptyItemsMiddleware(SomeStorageClass))
默认行为说明:
-
SomeStorageClass:你指定的 storage -
如果不传 storage:
- 默认使用 JSONStorage
middleware 的本质是:
在 storage 之上加一层"数据拦截与修改层"
它不会改变存储方式,而是:
- 在读数据时加工数据
- 在写数据时过滤或修改数据
使用 hooks 与覆盖机制
在某些情况下,仅靠自定义 storage 或 middleware 已经无法满足你对 TinyDB 的扩展需求。这时可以使用内置的 hooks 和 override 机制来修改其行为。
例如,可以通过修改默认表名来改变行为:
python
TinyDB.default_table_name = 'my_table_name'
TinyDB 的 TinyDB 和 Table 类都支持通过 hooks 和 override 进行行为修改。
例如,你可以通过 TinyDB.table_class 访问 Table 类,并修改其默认行为:
python
TinyDB.table_class.default_query_cache_capacity = 100
继承 TinyDB 与 Table
最后一种方式是直接通过继承来修改 TinyDB 的行为,这种方式比前面的扩展机制更底层,可以更深度地控制内部逻辑。
你可以通过创建子类来扩展 Table:
python
class MyTable(Table):
# Add your method overrides
...
然后将其注册到 TinyDB:
python
TinyDB.table_class = MyTable
# Continue using TinyDB as usual
TinyDB 的源码本身就围绕扩展性进行了设计,内部代码和方法都有详细注释,说明了各个机制的工作方式。
如果你需要更深层的自定义行为,可以直接阅读源码并按需修改实现。
结尾(Conclusion)
整体来看,TinyDB 给人的感觉非常一致:简单、直接、Pythonic。
它并没有构建一套复杂的数据库引擎,也没有引入传统意义上的查询优化器或执行计划系统。很多"看起来很高级"的能力,本质上都建立在 Python 自身已有的能力之上。
比如缓存机制,本质上就是对函数结果做了一层封装,很多场景可以直接对应到 Python 标准库中的 lru_cache 思路;而 storage(例如 JSONStorage),也只是对 JSON 文件的读写封装,并没有额外的存储引擎抽象。
再看 Query 系统,它看起来像 ORM,甚至有一点 DSL 的味道,但实际上并没有 SQL 引擎那种复杂的解析、优化与执行流程。它更像是:
一套基于 Python 表达式 + filter/map 的管道式过滤机制
本质上就是对 list / dict 数据做条件筛选,只是用了一种更优雅的表达方式包装起来而已。很多能力在逻辑上甚至等价于我们日常写的列表推导式或 filter 操作。
从代码规模来看,整个项目大约 2000 行左右(源码地址:https://github.com/msiemens/tinydb/tree/master),这也进一步说明了它的定位:不是一个完整数据库系统,而是一个轻量级的嵌入式数据操作工具。
如果换一个角度来看,我更倾向于把 TinyDB 理解为:
一个"带持久化能力的 dict + 查询过滤层"的组合体
它确实像数据库,但更本质的行为是围绕 Python 的字典结构进行增删查改,再叠加了一层查询表达能力。
这也解释了它的性能边界:由于所有查询本质上仍然是 Python 层的遍历与过滤,当数据量增长时,性能自然会线性下降,这一点与 SQLite 这种具备独立存储引擎与查询优化能力的系统完全不同。
虽然 SQLite 和 TinyDB 都常被归类为"轻量级数据库",但它们在架构复杂度上并不在一个层级:
- SQLite 是完整的数据库引擎(解析器 + 查询优化器 + 存储引擎)
- TinyDB 更像是 Python 世界里的"数据操作工具层"
总的来说:
TinyDB 的价值不在于"像数据库",而在于"用数据库的方式去组织 Python 数据操作"。
它适合轻量、嵌入式、快速开发的场景,但并不是为了替代传统数据库系统而设计的。