PyMongo 教程
>先看《MongoDB权威指南》
>本教程是官方教程的非专业翻译
教程
本教程的目的是介绍MongoDB和_PyMongo_
准备工作
在我们开始之前,首先确保已经安装PyMongo模块,然后尝试在python shell中运行以下语句,没有出现异常,说明pymongo已经可以正常工作了:
[shell] import pymongo
本教程还假设MongoDB实例已经正常运行在默认端口上。如果您已经下载并安装了MongoDB,可以像这样启动它:
$ mongod
建立链接
第一步就是使用PyMongo创建一个链接,用来链接到mongod实例:
[shell] from pymongo import Connection
[shell] connection = Connection()
上面的代码将会链接到默认的主机与端口。我们也可以指定主机和端口:
[shell] connection = Connection('localhost', 27017)
获取数据库
一个MongoDB实例可以支持多个独立的数据库。在PyMongo中,你可以使用属性风格来使用connection获得一个数据库:
[shell] db = connection.test_database
如果是因为数据库名称或其他什么原因不能使用属性风格来访问的话,可以使用字典风格来访问这个数据库:
[shell] db = connection['test-database']
获取集合
集合(Collection)是存放在MongoDB数据库中的一组文档,相当与关系型数据库中的表。获取一个集合与获取一个数据库的方法大致相同:
[shell] collection = db.test_collection
或者(使用字典风格):
[shell] collection = db['test-collection']
需要注意的是,上述的语句中的集合与数据库在MongoDB中都是延迟创建的,当执行其他操作时才会被真正的创建。集合与数据库将会在有第一个文档插入后真正创建。
文档
MongoDB中使用JSON风格的BSON文档(document)来表示数据。在PyMongo中,我们使用字典(dic)来表示一个文档(document)。例如,下面的字典就i表示一篇博客文章:
[shell] import datetime
[shell] post = {"author": "Mike",
... "text": "My first blog post!",
... "tags": ["mongodb", "python", "pymongo"],
... "date": datetime.datetime.utcnow()}
需要注意的是,上述代码中,要提交的文档包含了Python类型的数据(datetime.datetime
类型),它会被自动转换为适当的BSON类型。
插入文档
我们可以使用insert()
方法来将文档插入到一个集合中:
[shell] posts = db.posts
[shell] posts.insert(post)
ObjectId('...')
在插入文档时,如果没有显式指明_id
键,MongoDB会自动为_id
键产生一个ObjectId
类型的值;如果指明_id
键,那么请确保它的值在集合(collection)中是唯一。insert()
方法会在执行后返回文档的_id
值。
在插入第一个文档的过程中,上述代码中的posts集合实际上已经被创建了,我们可以列出数据库中所有的集合来验证一下前面提到过的延迟创建:
[shell] db.collection_names()
[u'posts', u'system.indexes']
注意: system.indexes集合是一个自动创建的特殊内部集合
使用find_one()
获取文档
在MongoDB中最常用的查询就是find_one()
。此方法返回一个匹配条件的文档(如果没有参数则不进行匹配)。在知道只有一个匹配文档或者只对第一个文档感兴趣的情况下,这个方法非常有用。现在我们从post集合获取地一个文档:
[shell] posts.find_one()
{u'date': datetime.datetime(...), u'text': u'My first blog post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'mongodb', u'python', u'pymongo']}
这个返回的字典与我们之前插入的第一条数据一样。
注意: 返回结果中的_id
是插入时自动创建的
find_one()
同时支持根据特定条件的查询。为了限制结果,我们现在只查询作者author为*Mike*的文档:
[shell] posts.find_one({"author": "Mike"})
{u'date': datetime.datetime(...), u'text': u'My first blog post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'mongodb', u'python', u'pymongo']}
我们可以尝试使用另一个不同的作者,比如Eliot,我们不会得到结果的,因为集合中唯一的一个文档不满足条件:
[shell] posts.find_one({"author": "Eliot"})
Unicode字符串
你可能会注意到返回结果的字符串与Python中默认的字符串有些不同(比如用u'Mike
来代替'Mike'
)。这里简短说明一下。
MongoDB以BSON格式存储数据,而BSON字符串使用的是UTF-8编码,所以PyMongo必须确保它存储的字符串为UTF-8格式。普通字符串(str
)的存储不变,unicode字符串会被PyMongo自动转为UTF-8格式。
批量插入
为了使查询更有趣,让我们插入几个文档。除了插入单个文档,我们也可以通过传入一个可迭代的参数(list
)批量插入多个文档。这样只使用一条命令将每次迭代的文档插入数据库:
[shell] new_posts = [{"author": "Mike",
... "text": "Another post!",
... "tags": ["bulk", "insert"],
... "date": datetime.datetime(2009, 11, 12, 11, 14)},
... {"author": "Eliot",
... "title": "MongoDB is fun",
... "text": "and pretty easy too!",
... "date": datetime.datetime(2009, 11, 10, 10, 45)}]
[shell] posts.insert(new_posts)
[ObjectId('...'), ObjectId('...')]
有一下几点比较有趣的事情需要注意:
1. insert()
的返回值包含了两个ObjectId
对象,每个都对应上面批量插入的文档
2. *new_posts[1]*与其他的posts看起来不一样:没有tags
,并且增加了一个新的title
。这里也证明了为什么我们一直说MongoDB是没有模式的
查询多个文档
要获得多个文档结果,我们使用find()
方法来查询。find()
返回一个Cursor
(游标)对象,它可以让我们遍历所有匹配的文档。例如,我们可以遍历所有posts集合的文档:
[shell] for post in posts.find():
... post
...
{u'date': datetime.datetime(...), u'text': u'My first blog post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'mongodb', u'python', u'pymongo']}
{u'date': datetime.datetime(2009, 11, 12, 11, 14), u'text': u'Another post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'bulk', u'insert']}
{u'date': datetime.datetime(2009, 11, 10, 10, 45), u'text': u'and pretty easy too!', u'_id': ObjectId('...'), u'author': u'Eliot', u'title': u'MongoDB is fun'}
find()
也可以像find_one()
那样来进行条件查询。现在,我们来查询所有作者author为*Mike*的文档:
[shell] for post in posts.find({"author": "Mike"}):
... post
...
{u'date': datetime.datetime(...), u'text': u'My first blog post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'mongodb', u'python', u'pymongo']}
{u'date': datetime.datetime(2009, 11, 12, 11, 14), u'text': u'Another post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'bulk', u'insert']}
计数
如果我们只是单纯的想知道有多少文件符合条件,我们可以执行count()
,而不用进行一次完整的查询。我们可以得到一个集合中所有文档的总数:
[shell] posts.count()
3
或者只统计符合条件的:
[shell] posts.find({"author": "Mike"}).count()
2
范围查询
MongoDB支持许多不同类型的高级查询。例如,我们只查询符合某一特定日期提交的文档,别且结果按作者author排序:
[shell] d = datetime.datetime(2009, 11, 12, 12)
[shell] for post in posts.find({"date": {"$lt": d}}).sort("author"):
... print post
...
{u'date': datetime.datetime(2009, 11, 10, 10, 45), u'text': u'and pretty easy too!', u'_id': ObjectId('...'), u'author': u'Eliot', u'title': u'MongoDB is fun'}
{u'date': datetime.datetime(2009, 11, 12, 11, 14), u'text': u'Another post!', u'_id': ObjectId('...'), u'author': u'Mike', u'tags': [u'bulk', u'insert']}
上面的代码中,我们使用了特殊操作符$lt
来限制条件,并且使用了sort()
方法来将结果以作者排序。
索引
为了使上面的查询速度快,我们可以在date和*author*上添加一个复合索引。首先,我们使用explain
工具来获取查询在没有使用索引情况下的一些信息:
[shell] posts.find({"date": {"$lt": d}}).sort("author").explain()["cursor"]
u'BasicCursor'
[shell] posts.find({"date": {"$lt": d}}).sort("author").explain()["nscanned"]
3
我们可以看到,当前查询使用BasicCurosr游标,说明没有使用索引;nscanned说明数据库查找了3个文档。现在让我们加上一个复合索引再看看:
[shell] from pymongo import ASCENDING, DESCENDING
[shell] posts.create_index([("date", DESCENDING), ("author", ASCENDING)])
u'date_-1_author_1'
[shell] posts.find({"date": {"$lt": d}}).sort("author").explain()["cursor"]
u'BtreeCursor date_-1_author_1'
[shell] posts.find({"date": {"$lt": d}}).sort("author").explain()["nscanned"]
2
现在的查询使用了BtreeCursor游标,说明使用了索引,并且索引存储在B树结构中;nscanned说明数据库只查找了2个符合条件的文档
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述