motor客户端需await初始化并用async with管理生命周期;insert返回值为对象而非字典,须用.inserted_id等属性访问;find返回游标须async for或to_list消费;update/delete参数顺序不可颠倒;应复用client实例避免连接池耗尽。

motor.AsyncIOMotorClient 初始化必须用 async with 或 await
直接 new 一个 motor.AsyncIOMotorClient 实例不等于连接就绪,它只是个“连接工厂”。真正建立连接、验证权限、获取数据库句柄,都得靠 await 触发。常见错误是写成:client = motor.AsyncIOMotorClient("mongodb://...") 然后立刻调 client.db.collection.find_one(...) —— 这会抛 RuntimeError: There is no current event loop in thread 或静默失败。
正确做法是:在协程里用 await client.admin.command("ping") 显式探测,或更稳妥地用 async with 管理生命周期(尤其配合测试或短生命周期服务):
import motor.motor_asyncio
import asyncio
<p>async def main():
client = motor.motor_asyncio.AsyncIOMotorClient("mongodb://localhost:27017")</p><h1>必须 await 才能真正触发连接</h1><pre class="brush:python;toolbar:false;">await client.admin.command("ping")
db = client["testdb"]
collection = db["users"]
# 后续操作才安全
result = await collection.insert_one({"name": "Alice"})
print(result.inserted_id)
insert_one / insert_many 返回值不是 dict,而是 InsertOneResult / InsertManyResult
很多人习惯性把 insert_one 的返回值当字典取 ["inserted_id"],结果报 TypeError: 'InsertOneResult' object is not subscriptable。Motor 的返回对象是封装类,字段名和 PyMongo 一致但访问方式不同。
-
insert_one返回InsertOneResult,要取 ID 得用.inserted_id属性 -
insert_many返回InsertManyResult,ID 列表在.inserted_ids(list 类型),不是.ids - 如果插入时没指定
_id,inserted_id是ObjectId;如果指定了字符串或整数,它就原样返回
示例:
result = await collection.insert_one({"_id": "user_123", "email": "a@b.com"})
print(result.inserted_id) # 输出: "user_123"
<p>docs = [{"name": "Bob"}, {"name": "Charlie"}]
result = await collection.insert_many(docs)
print(len(result.inserted_ids)) # 输出: 2</p>
find() 不返回数据,必须用 async for 或 to_list() 消费游标
collection.find(...) 返回的是 AsyncIOMotorCursor,不是 list 或结果集。直接 print 它只显示对象地址,不会触发查询。常见错误是写 data = collection.find({"age": {"$gt": 18}}) 然后以为 data 就是列表。
两种主流消费方式:
- 流式处理:用
async for doc in cursor:,内存友好,适合大数据量 - 一次性加载:用
await cursor.to_list(length=100),length=None表示不限数量(但生产环境慎用) - 注意
to_list()的length参数是最大条数,不是“取前 N 条”——如果设 10 但实际有 5 条,它就返回 5 条;设 5 但实际有 10 条,它只返回前 5 条
示例:
# 方式一:async for(推荐用于分页或大结果集)
async for user in collection.find({"status": "active"}):
print(user["name"])
<h1>方式二:to_list(适合已知小数据量)</h1><p>users = await collection.find({"role": "admin"}).to_list(length=10)
print(len(users))</p>
update_one / delete_one 的 filter 和 update 参数顺序不能颠倒
Motor 的 update_one 和 delete_one 方法签名是 method(filter, update_or_kwargs),和 PyMongo 一致。但新手常把参数顺序搞反,比如写成 await collection.update_one({"name": "Alice"}, {"$set": {"age": 30}}) 是对的,而写成 await collection.update_one({"$set": {"age": 30}}, {"name": "Alice"}) 就会误把更新操作当 filter,导致匹配不到文档且不报错(因为 {"$set": ...} 是合法 filter 语法,只是永远不匹配)。
几个关键点:
-
update_one第二个参数必须是更新操作符(如{"$set": {...}}、{"$inc": {...}}),不能是普通 dict;若想全量替换,需显式加upsert=False, replacement=True(但极少用) -
delete_one第二个参数不存在,只有filter和可选的session、comment等 keyword 参数 - 所有操作默认不抛异常(即使 filter 匹配 0 条),要判断是否成功,看返回对象的
.matched_count和.modified_count(update)或.deleted_count(delete)
示例:
result = await collection.update_one(
{"_id": "user_123"},
{"$set": {"last_login": datetime.utcnow()}}
)
if result.matched_count == 0:
print("用户不存在")
elif result.modified_count == 0:
print("数据未变更(可能新旧值一样)")
异步操作容易忽略连接池复用和超时配置。Motor 默认使用 100 连接的连接池,但如果你在 FastAPI 中每个请求都 new 一个 client,又不 close,很快会耗尽文件描述符。真正该复用的是 client 实例,而不是每次请求都重建。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











