前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >抛弃丑陋,拥抱优雅--Pythonic的Pony ORM

抛弃丑陋,拥抱优雅--Pythonic的Pony ORM

作者头像
哒呵呵
发布2018-08-06 11:28:34
3K1
发布2018-08-06 11:28:34
举报
文章被收录于专栏:鸿的学习笔记鸿的学习笔记

Pony ORM是一个设计的相当精巧的ORM框架,可以让你用Pythonic的方式去处理表数据,并且把ER图的思想融合进代码里。现在就看Pony ORM吧!

入门

首先你的安装一个Pony ORM

代码语言:javascript
复制
pip install pony

现在需要在脚本导入:

代码语言:javascript
复制
from pony.orm import *

当然你也可以不导入所有的模块,不过这样就必须要加orm前缀了

代码语言:javascript
复制
from pony import orm

在连接数据库之前,要有一个对象处理数据库所有的东西

代码语言:javascript
复制
db = Database()

现在假设我们有两个实体

代码语言:javascript
复制
class Person(db.Entity):
        name = Required(str)
        age = Required(int)
        cars = Set('Car')
代码语言:javascript
复制
class Car(db.Entity):
        make = Required(str)
        model = Required(str)
        owner = Required(Person)

这里的Person和Car绑定了db这个代表的数据库,Person拥有三个属性name,age,cars,Required表示name和age都是必须不为空,而Set表示这个字段和Car 这个类有关系并且是集合关系,之所以Car是String类型,因为Car在后面才会声明。

而Car这个实体拥有三个必须的属性make,model,owner,而owner必须是Person这个实体,这里可以理解为是一个一对多的关系,一辆车只有一个人,而一个人可以拥有多个Car。简简单单的通过代码,就将ER图的关系描绘出来了。多对多的关系的话,只要将Car的Required(Person)改成Set就可以了。

现在运行Person

代码语言:javascript
复制
show(Person)
代码语言:javascript
复制
class Person(Entity):
    id = PrimaryKey(int, auto=True)
    name = Required(str)
    age = Required(int)
    cars = Set(Car)

出现了上面的结果,注意到pony自动给你补上主键id,也意味着,你也可以使用PrimaryKey自定义主键。

数据库映射

有了两个实体,那么pony是怎么反映在数据库的呢?首先要绑定数据库

代码语言:javascript
复制
db.bind(provider='sqlite', filename=':memory:')

可惜的是目前Pony只支持四种数据库sqlite, mysql, postgresql and oracle

代码语言:javascript
复制
##### SQLite
db.bind(provider='sqlite', filename=':memory:')
db.bind(provider='sqlite', filename='database.sqlite', create_db=True)

#####  PostgreSQL
db.bind(provider='postgres', user='', password='', host='', database='')

#####  MySQL
db.bind(provider='mysql', host='', user='', passwd='', db='')

#####  Oracle
db.bind(provider='oracle', user='', password='', dsn='')

绑定了数据库,那就要将Car和Person映射过去了。

代码语言:javascript
复制
db.generate_mapping(create_tables=True)

create_tables=True代表如果Person和Cars没有对应的表,Pony会帮你在数据库建表,在generate_mapping之前必须要有实体,否则会报错。

设置debug模式,看pony帮我们生成的sql语句

代码语言:javascript
复制
set_sql_debug(True)
操作数据库

通过变量赋值的方式给数据库插入一些数据

代码语言:javascript
复制
p1 = Person(name='John', age=20)
p2 = Person(name='Mary', age=22)
p3 = Person(name='Bob', age=30)
c1 = Car(make='Toyota', model='Prius', owner=p2)
c2 = Car(make='Ford', model='Explorer', owner=p3)
commit()
代码语言:javascript
复制
GET CONNECTION FROM THE LOCAL POOL
BEGIN IMMEDIATE TRANSACTION
INSERT INTO "Person" ("name", "age") VALUES (?, ?)
['John', 20]

INSERT INTO "Person" ("name", "age") VALUES (?, ?)
['Mary', 22]

INSERT INTO "Person" ("name", "age") VALUES (?, ?)
['Bob', 30]

INSERT INTO "Car" ("make", "model", "owner") VALUES (?, ?, ?)
['Toyota', 'Prius', 2]

INSERT INTO "Car" ("make", "model", "owner") VALUES (?, ?, ?)
['Ford', 'Explorer', 3]

COMMIT

现在可以清楚的看到,Pony自动帮我们生成的insert语句,Pony不会立即将数据插入数据库,直到执行了commit() 语句,上面的所有执行才会进入到数据库里面。

如果你觉得写的麻烦,Pony还人性化的提供了db_session让你省却commit的烦恼。

代码语言:javascript
复制
@db_session
def print_person_name(person_id):
    p = Person[person_id]
    print(p.name)
    # database session cache will be cleared automatically
    # database connection will be returned to the pool

@db_session
def add_car(person_id, make, model):
    Car(make=make, model=model, owner=Person[person_id])
    # commit() will be done automatically
    # database session cache will be cleared automatically
    # database connection will be returned to the pool

除了省却commit步骤以外,db_session还能帮你在Exception时,自动回滚。

想更Pythonic一点的话,可以使用上下文管理器

代码语言:javascript
复制
with db_session:
    p = Person(name='Kate', age=33)
    Car(make='Audi', model='R8', owner=p)
    # commit() will be done automatically
    # database session cache will be cleared automatically
    # database connection will be returned to the pool
代码语言:javascript
复制
BEGIN IMMEDIATE TRANSACTION
INSERT INTO "Person" ("name", "age") VALUES (?, ?)
['Kate', 33]

INSERT INTO "Car" ("make", "model", "owner") VALUES (?, ?, ?)
['Audi', 'R8', 4]

COMMIT
RELEASE CONNECTION

你看Pony帮你把一切都做好了。

查询数据

查询数据使用了列表推导式,让你享有Python方便的一切

代码语言:javascript
复制
select(p for p in Person if p.age > 20)
代码语言:javascript
复制
<pony.orm.core.Query at 0x1f7ffb2bb38>

这是一个懒查询,和Python现在推崇的习惯一样,Pony只有等你需要的时候,才会真正的在数据库里执行这条SQL语句。

代码语言:javascript
复制
select(p for p in Person if p.age > 20)[:]
代码语言:javascript
复制
GET CONNECTION FROM THE LOCAL POOL
SWITCH TO AUTOCOMMIT MODE
SELECT "p"."id", "p"."name", "p"."age"
FROM "Person" "p"
WHERE "p"."age" > 20






[Person[2], Person[3], Person[4]]

继续来看看select有哪些方法?

代码语言:javascript
复制
dir(select(p for p in Person if p.age > 20))
代码语言:javascript
复制
[...
 'avg',
 'count',
 'delete',
 'distinct',
 'exists',
 'filter',
 'first',
 'for_update',
 'get',
 'get_sql',
 'limit',
 'max',
 'min',
 'order_by',
 'page',
 'prefetch',
 'random',
 'show',
 'sort_by',
 'sum',
 'to_json',
 'where',
 'without_distinct']

这里省略了不少方法,可以拿几个把玩下,例如order_by,首先赋值给一个变量

代码语言:javascript
复制
select(p for p in Person).order_by(Person.name)[:2]
代码语言:javascript
复制
SELECT "p"."id", "p"."name", "p"."age"
FROM "Person" "p"
ORDER BY "p"."name"
LIMIT 2






[Person[3], Person[1]]

如果想看到具体数据可以使用show方法

代码语言:javascript
复制
select(p for p in Person).order_by(Person.name)[:2].show()
代码语言:javascript
复制
SELECT "p"."id", "p"."name", "p"."age"
FROM "Person" "p"
ORDER BY "p"."name"
LIMIT 2

id|name|age
--+----+---
3 |Bob |30 
1 |John|20 

如果表本身具有关系,那么使用select会显示出这个关系

代码语言:javascript
复制
Car.select().show()
代码语言:javascript
复制
SELECT "c"."id", "c"."make", "c"."model", "c"."owner"
FROM "Car" "c"

id|make  |model   |owner    
--+------+--------+---------
1 |Toyota|Prius   |Person[2]
2 |Ford  |Explorer|Person[3]
3 |Audi  |R8      |Person[4]

甚至可以使用迭代的方式将数据取出来

代码语言:javascript
复制
persons = select(p for p in Person if 'o' in p.name)
for p in persons:
    print(p.name, p.age)
代码语言:javascript
复制
SELECT "p"."id", "p"."name", "p"."age"
FROM "Person" "p"
WHERE "p"."name" LIKE '%o%'

John 20
Bob 30

当然,如果你不想获得对象,那么返回的可以是具体的数据。

代码语言:javascript
复制
select(p.name for p in Person if p.age != 30)[:]
代码语言:javascript
复制
SELECT DISTINCT "p"."name"
FROM "Person" "p"
WHERE "p"."age" <> 30






['John', 'Mary', 'Kate']

或者返回是元组,甚至是使用Python自带的函数,只要你喜欢

代码语言:javascript
复制
select((p, count(p.cars)) for p in Person)[:]
代码语言:javascript
复制
SELECT "p"."id", COUNT(DISTINCT "car"."id")
FROM "Person" "p"
  LEFT JOIN "Car" "car"
    ON "p"."id" = "car"."owner"
GROUP BY "p"."id"






[(Person[1], 0), (Person[2], 1), (Person[3], 1), (Person[4], 1)]
代码语言:javascript
复制
max(p.age for p in Person)
代码语言:javascript
复制
SELECT MAX("p"."age")
FROM "Person" "p"






33
通过对象操作数据

作为Orm框架,自然也有面向对象的一面,例如

代码语言:javascript
复制
p1 = Person[1]

获得Person表的第一行数据

代码语言:javascript
复制
p1.name
代码语言:javascript
复制
'John'

拿到第一行的name列的数据

也可以指定条件获取数据

代码语言:javascript
复制
mary = Person.get(name='Mary')
代码语言:javascript
复制
SELECT "id", "name", "age"
FROM "Person"
WHERE "name" = ?
LIMIT 2
['Mary']
代码语言:javascript
复制
show(mary)
代码语言:javascript
复制
instance of Person
id|name|age
--+----+---
2 |Mary|22 

更新数据

代码语言:javascript
复制
mary.age += 1
commit()
代码语言:javascript
复制
BEGIN IMMEDIATE TRANSACTION
UPDATE "Person"
SET "age" = ?
WHERE "id" = ?
  AND "name" = ?
  AND "age" = ?
[24, 2, 'Mary', 23]

COMMIT
原生的SQL语句

如果你还觉得原生SQL更爽,Pony也能让你自如的写SQL语句

代码语言:javascript
复制
x = 25
Person.select_by_sql('SELECT * FROM Person p WHERE p.age < $x')
代码语言:javascript
复制
BEGIN IMMEDIATE TRANSACTION
SELECT * FROM Person p WHERE p.age < ?
[25]






[Person[1], Person[2]]

单个表不能满足你的话,也可以在整个数据库层面,使用数据

代码语言:javascript
复制
x = 20
db.select('name FROM Person WHERE age > $x')
代码语言:javascript
复制
select name FROM Person WHERE age > ?
[20]






['Mary', 'Bob', 'Kate']
小结

Pony ORM是一个优雅的框架,可以让你优雅的操作数据库,省却更多的烦恼,毕竟Python的宗旨就是让一切更优雅。

代码语言:javascript
复制
import this
代码语言:javascript
复制
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-07-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 鸿的学习笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 入门
  • 数据库映射
  • 操作数据库
  • 查询数据
    • 通过对象操作数据
      • 原生的SQL语句
        • 小结
        相关产品与服务
        云数据库 MySQL
        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档