只有 Python 魔法少女才知道的 PonyORM
开发项目时,经常会用到 ORM,而 Python 里能用的 ORM 其实也没几个,以我目前的开发经验来看,比较顺手的 ORM 有 Django 内置的那个,或是写 Flask 时,可以配合使用大佬封装好的 Flask-SQLAlchemy。而它们都与框架绑定,当你想在 Web 应用之外的项目里使用 ORM,体验就没这么优雅了。当你要直接使用 SQLAlchemy 时,它才向你展现出它的利爪和尖牙——这玩意实在是太特么难用了。
当然,人一切的痛苦,都是对自己无能的愤怒。SQLAlchemy 有着极其强大且丰富的功能,同时也有着与之相匹配的较高的复杂度和上手难度 (如果没有 Flask-SQLAlchemy 这样的东西,光是自己折腾 session 管理,就能耗费掉无数的青春),用不明白,只能说明自己菜。
面对着像大辞海一样的 SQLAlchemy 文档,我不禁陷入沉思——人活着到底是为了什么。ORM 原本是为了降低心智负担,提高开发效率和质量,而用这么复杂的一个东西,我的开发效率已经不知道低到哪里去了。更何况用 Flask 这样轻量化的框架,配合这样重量级的 ORM,怎样都觉得十分怪异。
直到我遇到了她。
PonyORM 是我最近开始尝试在实际项目中使用的 ORM,它优雅的使用方式,简单的上手难度,以及不错的开发效率,让我眼前一亮,相当 Pythonic!(战斗民族出品,值得信赖)
其实以前曾经听说过这玩意,但当时它是以 AGPLv3 授权,就没去关注,但在 2016 年底的 0.7 版本开始,改为了 Apache 2.0 许可,这样就友好了许多,值得一用了。
简单描述一下它的特性:
那么接下来以一个常见的博客应用为例,来看看如何使用这个 PonyORM,这里不涉及 web,仅包含与 orm 相关的部分。PonyORM 更复杂的使用方式以及介绍请参考它的文档。
Python
pip install pony
1
2
pip install pony
如果你用 MySQL,需要装个相应的底层驱动pymysql
,其他的数据库类似,具体参考文档 Connecting to the Database。
Python
from pony.orm import *
1
2
from pony . orm import *
Python
# 创建数据库对象 db = Database() # 建立数据库连接 db.bind(provider='mysql', host='localhost', user='user', passwd='pass', db='mydb')
1
2
3
4
5
6
# 创建数据库对象
db = Database ( )
# 建立数据库连接
db . bind ( provider = 'mysql' , host = 'localhost' , user = 'user' , passwd = 'pass' , db = 'mydb' )
Python
class Post(db.Entity): post_pk = PrimaryKey(int, auto=True) title = Required(str) content = Optional(LongStr) published_at = Required(datetime, default=datetime.now) categories = Set("Category") comments = Set("Comment") class Category(db.Entity): name = Required(str) posts = Set("Post") class Comment(db.Entity): post = Optional(Post) content = Required(str) published_at = Required(datetime, default=datetime.now)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Post ( db . Entity ) :
post_pk = PrimaryKey ( int , auto = True )
title = Required ( str )
content = Optional ( LongStr )
published_at = Required ( datetime , default = datetime . now )
categories = Set ( "Category" )
comments = Set ( "Comment" )
class Category ( db . Entity ) :
name = Required ( str )
posts = Set ( "Post" )
class Comment ( db . Entity ) :
post = Optional ( Post )
content = Required ( str )
published_at = Required ( datetime , default = datetime . now )
对于关联关系,它自动帮你生成外键,以及关联表。如果你没指定主键字段,它会生成一个默认的自增主键字段。
Python
db.generate_mapping(create_tables=True)
1
2
db . generate_mapping ( create_tables = True )
Python
with db_session: Post(title="第一篇文章", content="Hello world") commit()
1
2
3
4
with db_session :
Post ( title = "第一篇文章" , content = "Hello world" )
commit ( )
所有对数据库的读写都要在 db_sesion 中进行,除了通过 with 当作 context manager 使用,也可以作为装饰器,让 db_session 对整个函数中有效(并且 db_session 允许嵌套)
更新了数据记得 commit 提交。
Python
@db_session def create_post(title, content): Post(title=title, content=content) commit() create_post(title="第2篇文章", content="Hello world too") create_post(title="第3篇文章", content="Hello world 3")
1
2
3
4
5
6
7
8
@ db_session
def create_post ( title , content ) :
Post ( title = title , content = content )
commit ( )
create_post ( title = "第2篇文章" , content = "Hello world too" )
create_post ( title = "第3篇文章" , content = "Hello world 3" )
Python
with db_session: p = Post.get(post_pk=1) # 使用get来获取一条数据,如果没有查到会返回None p.content = "new content" commit()
1
2
3
4
5
with db_session :
p = Post . get ( post_pk = 1 ) # 使用get来获取一条数据,如果没有查到会返回None
p . content = "new content"
commit ( )
给第一篇 post 添加一些评论吧。
Python
with db_session: p = Post.get(post_pk=1) Comment(content="你瞅啥", post=p) Comment(content="瞅你咋地", post=p) commit() # 查看关联的数据 print(p.comments)
1
2
3
4
5
6
7
8
9
with db_session :
p = Post . get ( post_pk = 1 )
Comment ( content = "你瞅啥" , post = p )
Comment ( content = "瞅你咋地" , post = p )
commit ( )
# 查看关联的数据
print ( p . comments )
之后就可以通过p.comments
取到与之关联的评论。
那么再来试试多对多关系。
Python
with db_session: c1 = Category(name="tech") c2 = Category(name="blog") commit() Post(title="第5篇文章", content="Hello world too", categories=[c1]) Post(title="第6篇文章", content="Hello world 3", categories=[c1, c2]) commit() # 查看关联的数据 print(Category["tech"].posts) # 这个Category["tech"]等同于Category.get("tech") print(Post[6].categories)
1
2
3
4
5
6
7
8
9
10
11
12
13
with db_session :
c1 = Category ( name = "tech" )
c2 = Category ( name = "blog" )
commit ( )
Post ( title = "第5篇文章" , content = "Hello world too" , categories = [ c1 ] )
Post ( title = "第6篇文章" , content = "Hello world 3" , categories = [ c1 , c2 ] )
commit ( )
# 查看关联的数据
print ( Category [ "tech" ] . posts ) # 这个Category["tech"]等同于Category.get("tech")
print ( Post [ 6 ] . categories )
调用 Entity 实例的.delete()
方法可以删掉这条数据。如果需要把相关联的数据一并删掉,需要在定义 model 字段的时候加上cascade_delete=True
的参数。
Python
with db_session: Category["tech"].delete() commit()
1
2
3
4
with db_session :
Category [ "tech" ] . delete ( )
commit ( )
PonyORM 的查询方式比较魔性,和别的 ORM 有较大区别,这里给个简单的例子看看样子。
用 Entity 对象上的 select 方法,传入 lambda 表达式进行查询,查了 id 大于 2 并且内容包含 "world" 的条目。
Python
with db_session: query = Post.select(lambda p: p.post_pk > 2 and "world" in p.content) print(list(query)) # 将query对象转为list,触发真正的查询获取数据
1
2
3
4
with db_session :
query = Post . select ( lambda p : p . post_pk > 2 and "world" in p . content )
print ( list ( query ) ) # 将query对象转为list,触发真正的查询获取数据
使用另一种方式用 select 函数,传入一个生成器表达式作为参数,查询了以 "咋地" 结尾的 Comment。
Python
with db_session: query = select(p.content for p in Comment if p.content.endswith("咋地")) print(query[:]) # 另一种转list的方式
1
2
3
4
with db_session :
query = select ( p . content for p in Comment if p . content . endswith ( "咋地" ) )
print ( query [ : ] ) # 另一种转list的方式
使用 SQL 直接查询。
Python
with db_session: query = Category.select_by_sql("SELECT * FROM category LIMIT 1") print(query[:])
1
2
3
4
with db_session :
query = Category . select_by_sql ( "SELECT * FROM category LIMIT 1" )
print ( query [ : ] )
PonyORM 的魔法黑到飞起,写起来还是比较爽的,但由于没有太过深入使用过这个框架,这些黑魔法在复杂查询时效果未知(实在不行你可以直接写 raw sql 嘛,感觉也没太大问题)。我目前在公司的项目中已经用它替换掉了 SQLAlchemy,感觉良好,运行比较平稳,还没踩到什么坑。(FLAG)或者通过阅读源码,你可以学会更多骚操作,www。再不济也起码证明了自己对彩虹小马的信仰嘛~
亲爱的朋友,欢迎你也成为爱马仕,一起踩坑~★
手机扫一扫
移动阅读更方便
你可能感兴趣的文章