声明
本篇主要讲,关于peewee的一些拓展:
包括新式CRUD-数据导入导出-信号-数据库反向生成模型。
扩展官档:http://docs.peewee-orm.com/en...
作者友好 与 peewee提问方式
当我用到拓展模块的 新CRUD时,文档给的内容少之又少。
因为拓展的新CRUD是真的方便好用,和(PyMongo的用法差不多)
但是功能却不全。并且与我们第二篇,讲的CRUD又不兼容。
所以在难以取舍之际, 我选择了提问。
peewee作者在官档中详细说到。 如果你有问题或疑惑可以通过以下两种方式:
- 去stack overflow 提问题,标签打上python 和 peewee。 peewee作者会不定期浏览并回答你给你帮助。
- 去https://groups.google.com/gro...,这个google群组提问。(需要科学上网)
所以我选择了去 stack overflow提问。
我问题发出去,应该是不到一小时,作者就给回复了,我惊了。。(发完我就睡觉了,第二天起来才看到)
提问内容传送门如下:https://stackoverflow.com/que...
作者回复的意思是:
拓展的playhouse.dataset里面的DataSet 的 新式CRUD API 的涉及初衷就是为了简单使用。
但它并不会代替 核心CRUD (就是我们第二篇讲的CRUD)
并且,它设计的初衷就是让我们可以方便 (json/csv格式的数据 与 数据库的数据 相互导入或导出)
我说的这些操作,下面都会写到。
所以说,该提问就提问,你收获了peewee知识的同时,又能增加peewee的社区活跃度。
playhouse扩展模块的DataSet
我们前2篇文章就用了这一行代码就可以导入所有,因为所有基本功能都集成在 peewee下
from peewee import *
但接下来讲的是扩展,而扩展就是新分支了,与peewee没关系了
from playhouse.dataset import DataSet
数据库连接
进入正题,连接MySQL,你可以有两种连接方式:
# 强调一下,官档中给出 DataSet 是在 playhouse.dataset 下
# 我再强调一下, 是 DataSet , 而不是 DataBase !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from playhouse.dataset import DataSet
db = DataSet('mysql://root:[email protected]:3306/test')
官档-所有数据库的连接示例: http://docs.peewee-orm.com/en...
数据库与表的基本操作
创建表
owner = db['owner']
# 后面这个"owner" 是你自己指定的key, 也就是你指定的 "表名"
# 前面这个 owner 变量用来接受返回结果,它就是一个实例化的表对象。 一会我们用它做一系列操作
我讲完了,表已经创建好了, (你脸上有没有出现问号。。) 惊不惊喜,意不意外??
题外话:
如果你用过,pymongo来使用MongoDB, 那这种原理应该不陌生的。
那 owner = db['owner'] 这一行代码究竟做了什么?
1. 它会去数据库,创建一个 名为 owner的表 (有则返回,无则创建)
2. 不但创建了表, 而且它还在表中 自动创建了一个 id (int型,主键,自增)
列出表 & 列出字段
列出表(等价于MySQL中的命令: show tables)
print(db.tables)
>> ['new_owner', 'owner']
列出表中的字段(等价于MySQL中的命令: show columns):
table = db['owner'] # 先选择一个表。
print(table.columns)
>> ['id', 'name', 'age', 'gender', 'hobby', 'nickname']
统计表中记录数(就是行数)
print(len(table))
>> 10
事务(transaction)
其实我在第一篇,已经讲过事务了
事务-传送门: https://segmentfault.com/a/11...
当时我是用的 MySQLDatabase 连接工具。
我只讲了一种使用方法: db.atomic()
其实还有另外一种使用方法,就是 db.transaction()
这两种方法差不多, 你可以这么认为,就是把 atomic 和 transaction 单词换一下,用法一模一样
而现在我们是使用的 DataSet 连接工具 (开篇我强调过):
db = DataSet(....)
DataSet 只提供给我们 transaction()这种用法, 而没有提供 atomic()
但我说了,这两种用法你可以认为只是单词换了一下。用法一样的。
因此你完全可以直接看我前面给的 "事务-传送门" 这篇之前写的文章。
CRUD (这种CRUD方式我不太推荐)
- 我们第二章详细讲过全套的CRUD,其实那就够了。
- 而本章这个拓展的CRUD,完全是另一种模式。另一个模块的东西。
- 但我不推荐用,因为感觉还没成熟,不完善(很多CRUD细节功能没有), 官档给的也粗略。
- 此外,下面涉及到与 本套CRUD有关联的操作, 我下面统称为 "拓展的CRUD"
增加数据
owner.insert(name='Alice', age=20)
owner.insert(name='Zhang', age=18)
这看起来没什么问题,我们之前讲的差不多。但你有没有意识到几个问题:
1. owner表 是自动创建出来的,它只有一个主键。
2. 我们没有创建 name 和 age 字段
3. 既然没有创建字段,为什么可以插入数据???
解惑:
1. 我们调用第一个 insert()的时候, 它就会自动帮我们 去数据库创建对应参数的字段(固定了):
数据类型关系对照如下:
python数据类型 MySQL数据类型
int int(11) 允许为空 默认值未空
str text 允许为空 默认值未空
那peewee为我们自动创建的字段如下:
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | text | YES | | NULL | |
| age | int(11) | YES | | NULL | |
+-------+---------+------+-----+---------+----------------+
2. 创建后,它还会自动为我们根据我们传的参数, 对应的插入 值
3. 特别注意,特别注意:
假如我们插入这样一条错误"类型"的数据:
owner.insert(name=18, age=18) # 按理说name应该为 text型,age应该为整形
虽然我们name给的整形,但,peewee内部会自动为我们转为将 name转为字符串类型
同理 字符串也会自动帮我们转为整形(对应数据库中的类型)。 但'abc'这样的转不了哦
修改数据
owner.update(name='Alice', age='50')
这行代码为我们做了如下事情:
将"所有"行数据, name改为 Alice, age改为 50
owner.update(name='Alice', age='50',columns=['name'])
多加了一个 columns参数,为我们做了如下事情:
先看最后的 columns=['name'] : 其实他就等价于 where name = xxx
其实意思就是以 "name字段为条件" 修改数据:(而name我们已经给了 = 'Alice')
具体过程如下:
1. 找到 name = Alice, 的所有记录
2. 将 name= Alice 的记录 name改为Alice(相当于没变), age 改为 50
owner.update(gender='man')
你仔细看看,我们之前是没有 gender这个字段的。 如果更新未创建字段,它会为我们做如下事情:
1. 它会自动为我们在这个表中创建一个 这个 "gender新字段"
2. 并且,"所有行记录" 都会被赋予成 "man" 值。 注意是 所有行,所有行,所有行
3. 如果你只想"给指定记录部分"赋予 "man"值, 那你可以加一个我们之前说的 "columns参数"
自然而然地,其他 "未通过 columns指定的记录" 就会被赋予 NULL 值。
查询数据
首先说一下它这个查询的特色。
- 可以对查询结果进行 切片,索引操作(和python的切片和索引是一模一样的)
- 索引之后自动转为 列表嵌字典 [{}, {}]
- 下面3种方式,也都支持 切片和索引(我就不举例了,很简单)
方式1:all() 获取全部数据
owner = db1['owner']
query = owner.all()
for obj in query:
print(obj) # 遍历每条记录, 结果是 字典 类型
方式2:find() 查询符合条件的所有数据
query = owner.find(name='Tom') # 查询名为 Tom的数据
for obj in query:
print(obj) # 遍历每条记录, 结果是 字典 类型
方式3:find_one() 查询符合条件的 第一条 数据
print(owner.find_one(name='Tom'))
注意一下,find_one 查出来结果就是一条字典。 不要再遍历了,遍历就出事了。。。
删除数据
result = owner.delete(name='Tom')
说明:
1. 若 delete() 不指定参数,那么即为全部删除。。慎用
2. 返回值result的值, 代表删除数据的条数
Json/CSV数据 导入到数据库(只能联用拓展的CRUD)
Json实例
我在当前目录下创建一个 new_owner.json, 内容如下:
[
{"name": "Alice", "age": 18},
{"name": "Zhang", "age": 30}
]
主文件代码如下:
new_owner = db['new_owner'] # 新建一张表,名为 new_owner
new_owner.thaw(
filename='new_owner.json', # 就是上面的那个 json文件
format='json' # 指定格式为json, 默认值是 csv
)
这样就导入好了,数据库内容如下:
+----+------+-------+
| id | age | name |
+----+------+-------+
| 1 | 18 | Alice |
| 2 | 30 | Zhang |
+----+------+-------+
特殊情况分析: 假如基于上面创建好的数据库与数据, 将刚刚的json文件稍加改动,加个 "gender"
[
{"name": "Alice", "age": 18, "gender": "man"},
{"name": "Zhang", "age": 30}
]
假如我们还是用上面的一模一样的主文件代码(我这里就不重复写了),导入刚修改的 新json数据。
我们分析一下:
我说了,是在原有表 和 字段的 基础上去导入新数据。 (原有数据库字段为 name 和 age)
但是今时不同往日。。。 我们刚刚的json新添加了一个, gender。
而原有的数据库中,并没有这个字段。 如果我们还是用之前的这个代码:
new_owner.thaw(
filename='new_owner.json', # 就是上面的那个 json文件
format='json' # 指定格式为json, 默认值是 csv
)
那么,peewee会自动把这个 新 gender 键,同步到数据库,并生成 新字段 gender
+----+------+-------+--------+
| id | age | name | gender |
+----+------+-------+--------+
| 1 | 18 | Alice | NULL |
| 2 | 30 | Zhang | NULL |
| 3 | 18 | Alice | man |
| 4 | 30 | Zhang | NULL |
+----+------+-------+--------+ 看见了把,gender字段,会自动生成。
但是在某种情况下, 你并不想让数据库 创建这个 新字段
假如json中 "gender" , 它属于一个脏数据,我们不需要他,而是单纯的想插入 name和age数据。
那么你可以,添加一个 strict=True 参数:
new_owner.thaw(
filename='new_owner.json',
format='json',
strict=True # 看这里,添加一个这个 strict=True就好了
)
strict=True意味着,它会对照json的key 与 数据库的字段, 并以数据库的字段为主,严格匹配插入。
CSV示例
其实和json的几乎差不多,注意csv格式,逗号分隔,设置表头。 new_owner.csv 内容如下:
name,age
Alice,18
Zhang,30
主文件代码如下
new_owner.thaw(
filename='owner.csv',
# format='csv' # 我说过,format默认参数就是csv,所以给不给format参数都行。
# strict=True # 严格匹配字段插入, 和上面讲的json是一模一样的作用。
)
最后说个小细节,不知道你有没有注意,我们json和csv文件, 都没有指定id。
虽然没有指定,但是 peewee 同样会为我们自动创建 id (同样也是 int(11), 主键, 自增)
数据库数据导入到Json/CSV(只能联用拓展的CRUD)
导入都说完了,导出就更简单了。。就API变个名的事:
导出单个表的全部数据:
owner = db['owner']
owner.freeze(
filename='new_owner.json',
format='json', # 指定格式为json, 你要是不指定,默认值是 csv
)
当然,这里有个特色,导出方式还可以 导出某个查询结果!!
query = owner.find(name='Tom')
db.freeze(
query, # 查询结果,注意这个查询结果,必须是个查询集类型。
filename='new_owner.json',
format='json', # 指定格式为json, 你要是不指定,默认值是 csv
)
中场暂停。。。
至此,playhouse.dataset的DataSet里面的 新版CRUD,及其附属功能(json导入导出等)讲完。
也许你会很不适应。 (这新版的CRUD,如果实在不能掌握,就当了解即可)
最重要的还是第二篇文章的CRUD:https://segmentfault.com/a/11...
以下要讲的,就是 第二篇文章讲的,正常的 (from peewee import * )里面的 CRUD 相关的操作了。
信号(Signal)
官方只设定了如下 4 种信号:
一、pre_save: 保存之前调用
二、post_save:保存之后调用
pre_save 和 post_save只支持下面两种API:
1. create() # 创建数据
触发:
Owner.create(name='Tom') # create包含了 save() ,所以会自动触发
2. save() # 更新数据
触发:
obj = Owner.get(name='Butch')
obj.name = 'Alice'
obj.save()
注意: 你想用保存信号,就必须用这两种API, 用 update()是不好使的哦!!!!!!!!!!
三、pre_delete: 删除数据之前调用
四、post_delete:删除数据之后调用
pre_delete 和 post_delete 只支持一种 API ,那就是 delete_instance()
触发:
objs = Owner.select().where(Owner.name=='lin')
for obj in objs:
obj.delete_instance()
接下来我们开始看代码如何写:
先把表和数据构造出来,还是老方式:
from peewee import *
# 注意是 playhouse里的Model, 以及前面提到的 4 种 信号
from playhouse.signals import Model, post_save, pre_save, pre_delete, post_delete
db = MySQLDatabase('test', user='root', password='123',
host='IP', port=3306, charset='utf8mb4')
class Owner(Model):
"""
特别注意,这个Model, 使用是playhouse.signals下的 Model
而不是 peewee 下的 Model, 这需要特别注意
两个模块都有Model, 所以把用的Model放在 相对偏下面导入
"""
data = IntegerField() # 而字段依然是 from peewee import * 导入的
class Meta:
database=db
db.create_tables([Owner])
进入正题:信号使用有两种方式:
方式1(装饰器方式):
# 前面的4种信号的用法就是用来,装饰一个自定义函数。
# 这个自定义函数就是信号出发之后,为我们做事的。
# 接下来我以 post_save 为例 (当然我这个例子只是强调一下语法,并没有实用价值)
@pre_save(sender=Owner) # sender指定 我们的模型类
def aaa(model_class, instance, created=True): # 这个名字随便起
"""
model_class: 就是 Owner这个类,默认传进来方便你使用
instance 和 create=True 照着写上就行不用管它
"""
print(f"数据入库之前我们捕获了此表名=>{model_class}")
for obj in model_class.select().dicts(): # model_class就是Owner类,它可以任意CRUD
print(f"再次查看此表信息{obj}")
触发信号:
obj = Owner.get(name='Tom')
obj.name = 'Rose'
obj.save()
方式2 (函数连接):
信号-官档:http://docs.peewee-orm.com/en...
其实你用 方式1 就行了。
数据库反向生成 Python模型类
参数官档:http://docs.peewee-orm.com/en...
看好参数大小写就行(test是我的数据库名):
python -m pwiz -e mysql -H 192.6.6.6 -p 3306 -u root -P test > mymodel.py
# 指定了 -P ,密码是后续命令行 追入的。
模型迁移 migration
我没怎么看,需要的自己瞅瞅。。
模型迁移-官档:http://docs.peewee-orm.com/en...
结束语
其实ORM都差不多。
Django:的ORM还算可以。但是不太好脱离框架单独使用 (相当于与Django平级)。
sqlalchemy:没怎么用过。之前看过几眼。感觉极度不适。感觉学习成本有点高(相当于与Python平级)
peewee:是一个可单独使用的简便的ORM框架(写web爬虫之类的都能用得上,相当于与Python平级)
我感觉:如果ORM的学习成本大于SQL的学习成本, 那倒不如精修一下SQL,即使换了环境也能用得上。
有时候高阶ORM用多了,可能连SQL都不会写了。。。 (It's just for me......)
第一篇传送门:https://segmentfault.com/a/11...
第二篇传送门:https://segmentfault.com/a/11...