Python:轻量级 ORM 框架 peewee 用法详解之——增删改查

栏目: Python · 发布时间: 6年前

内容简介:说明:本文中代码样例所使用的

说明: peewee 中有很多方法是 延时执行 的,需要调用 execute() 方法使其执行。下文中不再特意说明这个问题,大家看代码。

本文中代码样例所使用的 Person 模型如下:

class Person(Model):
    Name = CharField()
    Age = IntegerField()
    Birthday = DateTimeField()
    Remarks = CharField(null=True)
复制代码

一、新增

1、create

Model.create 向数据库中插入一条记录,并返回一个新的实例。

p = Person.create(Name='张三', Age=30, Birthday=date(1990, 1, 1))
复制代码

2、save

语法:

save(force_insert=False, only=None)
复制代码

参数:

  • force_insert:是否强制插入
  • only(list):需要持久化的字段,当提供此参数时,只有提供的字段被持久化。

示例:

p1 = Person(Name='王五', Age=50, Birthday=date(1970, 1, 1))
p1.save()
复制代码

这里说的比较简单,下面会详细说明。

3、insert

insert 只插入数据而不创建模型实例,返回新行的主键。

Person.insert(Name='李四', Age=40, Birthday=date(1980, 1, 1)).execute()
复制代码

4、insert_many

语法:

insert_many(rows, fields=None)
复制代码

参数:

  • rows:元组或字典列表,要插入的数据
  • fields(list):需要插入的字段名列表。

说明: 1、当 rows 传递的是字典列表时,fields 是不需要传的,如果传了,那么,rows 中的字段在字典中必须存在,否则报错。如果没有传递 fields 参数,那么默认取所有字典的交集作为插入字段。这个也好理解,比如一个字典的键是 a、b、c ,一个是 b、c、d ,那么就取 b、c 作为需要插入的字段。 peewee 不会为缺失的字段做默认处理。 2、当 rows 传递的是元组列表时,必须指定 fields,并且 fields 中字段名的顺序跟元组一致。元组中值的数量必须大于等于 fields 中字段的数量,一般建议是保持一致。

示例:

Person.insert_many([
    ('张三', 30, date(1990, 1, 1)),
    ('李四', 40, date(1980, 1, 1)),
    ('王五', 50, date(1970, 1, 1))
],
    ['Name', 'Age', 'Birthday']
).execute()

Person.insert_many([
    {'Name': '张三', 'Age': 30, 'Birthday': date(1990, 1, 1)},
    {'Name': '李四', 'Age': 40, 'Birthday': date(1980, 1, 1)},
    {'Name': '王五', 'Age': 50, 'Birthday': date(1970, 1, 1)}
]
).execute()
复制代码

对于批量操作,应该放在事务中执行:

with db.atomic():
    Person.insert_many(data, fields=fields).execute()
复制代码

在使用批量插入时,如果是 SQLite,SQLite3 版本必须为 3.7.11.0 或更高版本才能利用批量插入API。此外,默认情况下,SQLite 将 SQL 查询中的绑定变量数限制为 999。

SQLite 中,当批量插入的行数超过 999 时,就需要使用循环来将数据批量分组:

with db.atomic():
    for idx in range(0, len(data), 100):
        Person.insert_many(data[idx: idx+100], fields=fields).execute()
复制代码

Peewee 中带有一个分块辅助函数 chunked() ,使用它可以有效地将通用迭代块分块为一系列批量迭代的迭代:

from peewee import chunked

# 一次插入 100 行.
with db.atomic():
    for batch in chunked(data, 100):
        Person.insert_many(batch).execute()
复制代码

5、bulk_create

语法:

bulk_create(model_list, batch_size=None)
复制代码

参数:

  • model_list (iterable):未保存的模型实例的列表或其他可迭代对象。
  • batch_size (int):每次批量插入的行数。如果未指定,则一次性全部插入。

示例:简单来说, insert_many 使用字典或元组列表作为参数,而 model_list 使用模型实例列表作为参数,就这区别。

data = [Person(Name='张三~', Age=30, Birthday=date(1990, 1, 1)),
        Person(Name='李四~', Age=40, Birthday=date(1980, 1, 1))]
with db.atomic():
    Person.bulk_create(data)
复制代码

**注意:**如果使用的是 Postgresql(支持该RETURNING子句),则先前未保存的模型实例将自动填充其新的主键值。

例如用的是 SQLite,执行上述代码之后, print(data[0].id) 显示的结果是 None

6、batch_commit

这不是一个好的方法,来看下面的例子

data_dict = [{'Name': '张三', 'Age': 30, 'Birthday': date(1990, 1, 1)},
             {'Name': '李四', 'Age': 40, 'Birthday': date(1980, 1, 1)},
             {'Name': '王五', 'Age': 50, 'Birthday': date(1970, 1, 1)}]

for row in db.batch_commit(data_dict, 100):
    p = Person.create(**row)
复制代码

查看 SQL 语句如下:

('BEGIN', None)
('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['张三', 30, datetime.date(1990, 1, 1)])
('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['李四', 40, datetime.date(1980, 1, 1)])
('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['王五', 50, datetime.date(1970, 1, 1)])
复制代码

其实, batch_commit 就是自动添加了一个事务,然后一条条的插入,所以返回的模型实例中能获取到主键。 参数第一个是字典列表,第二个就是每多少条启用一个事务,大家可以把它改成 1 看下 SQL 语句就明白了。

7、insert_from

使用 SELECT 查询作为源 INSERT 数据。此 API 应用于 INSERT INTO ... SELECT FROM ... 形式的查询。

语法:

insert_from(query, fields)
复制代码

参数:

  • query:SELECT查询用作数据源
  • fields:要将数据插入的字段,此参数必须要的 **示例:**我们将 Person 表按原结构复制一个 Person2 表出来,以做演示。
data = Person.select(Person.Name, Person.Age, Person.Birthday)
Person2.insert_from(data, ['Name', 'Age', 'Birthday']).execute()
复制代码

注意:因为是 INSERT INTO ... SELECT FROM ... 形式的,所以数据源的列跟要插入的列必须保持一致。

二、删除

1、delete

delete 后加 where 删除指定记录,如果不加 where ,则删除全部记录。

Person.delete().where(Person.Name=='王五').execute()
复制代码

2、delete_instance

删除给定的实例。 语法:

delete_instance(recursive=False, delete_nullable=False)
复制代码

示例:

p = Person.get(Person.Name=='张三')
p.delete_instance()
复制代码

delete_instance 直接执行删除了,不用调用 execute() 方法。

参数:一般我都是先讲参数再讲示例的,这次倒过来,示例其实很简单,一看就明白。但是这个参数缺需要好好讲下。

这两个参数都跟外键有关。我们修改一下测试用的模型。假设有这样两个模型,一个人员,一个部门,人员属于部门。

class Department(Model):
    Name = CharField()

    class Meta:
        database = db


class Person(Model):
    Name = CharField()
    Age = IntegerField()
    Birthday = DateTimeField()
    Remarks = CharField(null=True)
    Department = ForeignKeyField(Department, null=True) # 这里外键可为空和不可为空是不一样的,下面说明

    class Meta:
        database = db
复制代码

① 当 recursive=False 时,只删除了【部门】,【人员】没有影响,从 SQL 语句中可以看出。

d = Department.get(1)
d.delete_instance(recursive=False)

# 执行的 SQL 语句
('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0])
('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
复制代码

② 当 recursive=True ,并且外键 不可为空 时,会先删除【部门】下的【人员】,再删除【部门】。

d = Department.get(1)
d.delete_instance(recursive=True)

# 执行的 SQL 语句
('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0])
('DELETE FROM "person" WHERE ("person"."Department_id" = ?)', [1])
('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
复制代码

③ 当 recursive=True ,并且外键 可为空 时,先将【人员】的【部门ID(外键字段)】置为了 NULL ,再删除【部门】。

d = Department.get(1)
d.delete_instance(recursive=True)

# 执行的 SQL 语句
('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0])
('UPDATE "person" SET "Department_id" = ? WHERE ("person"."Department_id" = ?)', [None, 1])
('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
复制代码

delete_nullable 仅在 recursive=True 且外键 可为空 时有效,和 ③ 一样,当 delete_nullable=True 时,会删除【人员】,而不是将【人员的部门ID】置为 NULL

d = Department.get(1)
d.delete_instance(recursive=True, delete_nullable=True)

# 执行的 SQL 语句
('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0])
('DELETE FROM "person" WHERE ("person"."Department_id" = ?)', [1])
('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
复制代码

三、修改

1、save

之前说过, save() 方法可以插入一条记录,一旦模型实例具有主键,任何后续调用  save() 都将导致 UPDATE 而不是另一个 INSERT 。模型的主键不会改变。

p = Person(Name='王五', Age=50, Birthday=date(1970, 1, 1))
p.save()
print(p1.id)
p.Remarks = 'abc'
p.save()
复制代码

这个例子,第一次执行的 saveINSERT ,第二次是 UPDATE

这里解释一下, Person 这个模型,我并没有指定主键, peewee 会自动增加一个名为 id 的自增列作为主键。在执行第一个 save() 方法的时候,主键没值,所以执行 INSERTsave() 方法执行之后,自增列的值就返回并赋给了模型实例,所以第二次调用 save() 执行的是 UPDATE 。 如果模型中一开始就用 PrimaryKeyFieldprimary_key 指定了主键,那么 save 执行的永远都是 update ,所以什么主键不存在则 INSERT ,存在则 UPDATE 这种操作根本不存在,只能自己来写判断。

2、update

update 用于批量更新,方法相对简单,以下三种写法都可以

# 方法一
Person.update({Person.Name: '赵六', Person.Remarks: 'abc'}).where(Person.Name=='王五').execute()

# 方法二
Person.update({'Name': '赵六', 'Remarks': 'abc'}).where(Person.Name=='张三').execute()

# 方法三
Person.update(Name='赵六', Remarks='abc').where(Person.Name=='李四').execute()
复制代码

3、原子更新

看这样的一个需求,有一张表,记录博客的访问量,每次有人访问博客的时候,访问量+1。

因为懒得新建模型,我们就以 Person 模型的 Age + 1 来演示。

我们可以这样来写:

for p in Person.select():
    p.Age += 1
    p.save()
复制代码

这样当然是可以实现的,但是这不仅速度慢,而且如果多个进程同时更新计数器,它也容易受到竞争条件的影响。

我们可以用 update 方法来实现。

Person.update(Age=Person.Age+1).execute()
复制代码

四、查询

1、get

Model.get() 方法检索与给定查询匹配的单个实例。 语法:

get(*query, **filters)
复制代码

参数:

  • query:查询条件
  • filters:Mapping of field-name to value for Django-style filter. 我翻遍网上文章和官方文档都没找到这玩意怎么用!

示例:

p1 = Person.get(Name='张三')
复制代码

或者

p2 = Person.get(Person.Name == '李四')
复制代码

当获取的结果不存在时,报 Model.DoesNotExist 异常。如果有多条记录满足条件,则返回第一条。

2、get_or_none

如果当获取的结果不存在时,不想报错,可以使用 Model.get_or_none() 方法,会返回 None ,参数和 get 方法一致。

3、get_by_id

对于主键查找,还可以使用快捷方法 Model.get_by_id()

Person.get_by_id(1)
复制代码

4、get_or_create

Peewee 有一个辅助方法来执行“获取/创建”类型的操作:  Model.get_or_create() 首先尝试检索匹配的行。如果失败,将创建一个新行。

p, created = Person.get_or_create(Name='赵六', defaults={'Age': 80, 'Birthday': date(1940, 1, 1)})
print(p, created)

# SQL 语句
('SELECT "t1"."id", "t1"."Name", "t1"."Age", "t1"."Birthday", "t1"."Remarks" FROM "person" AS "t1" WHERE ("t1"."Name" = ?) LIMIT ? OFFSET ?', ['赵六', 1, 0])
('BEGIN', None)
('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['赵六', 80, datetime.date(1940, 1, 1)])
复制代码

参数: get_or_create 的参数是 **kwargs ,其中 defaults 为非查询条件的参数,剩余的为尝试检索匹配的条件,这个看执行时的 SQL 语句就一目了然了。对于“创建或获取”类型逻辑,通常会依赖唯一 约束或主键来防止创建重复对象。但这并不是强制的,比如例子中,我以 Name 为条件,而 Name 并非主键。只是最好不要这样做。

返回值: get_or_create 方法有两个返回值,第一个是“获取/创建”的模型实例,第二个是是否新创建。

5、select

使用 Model.select() 查询获取多条数据。 select 后可以添加 where 条件,如果不加则查询整个表。

语法:

select(*fields)
复制代码

参数:

  • fields:需要查询的字段,不传时返回所有字段。传递方式如下例所示。

示例:

ps = Person.select(Person.Name, Person.Age).where(Person.Name == '张三')
复制代码

select() 返回结果是一个 ModelSelect 对象,该对象可迭代、索引、切片。当查询不到结果时,不报错,返回 None 。并且 select() 结果是延时返回的。如果想立即执行,可以调用 execute() 方法。

注意: where 中的条件不支持 Name='张三' 这种写法,只能是 Person.Name == '张三'

6、获取记录条数 count 方法

使用 .count() 方法可以获取记录条数。

Person.select().count()
复制代码

也许你会问,用 len() 方法可以吗?当然也是可以的,但是是一种不可取的方法。

len(Person.select())
复制代码

这两者的实现方式天差地远。用 count() 方法,执行的 SQL 语句是:

('SELECT COUNT(1) FROM (SELECT 1 FROM "person" AS "t1") AS "_wrapped"', [])
复制代码

而用 len() 方法执行的 SQL 语句却是:

('SELECT "t1"."id", "t1"."Name", "t1"."Age", "t1"."Birthday", "t1"."Remarks" FROM "person" AS "t1"', [])
复制代码

直接返回所有记录然后获取长度,这种方法是非常不可取的。

7、排序 order_by 方法

Person.select().order_by(Person.Age)
复制代码

排序默认是升序排列,也可以用 +asc() 来明确表示是升序排列:

Person.select().order_by(+Person.Age)
Person.select().order_by(Person.Age.asc())
复制代码

-desc() 来表示降序:

Person.select().order_by(-Person.Age)
Person.select().order_by(Person.Age.desc())
复制代码

如要对多个字段进行排序,逗号分隔写就可以了。

五、查询条件

当查询条件不止一个,需要使用逻辑运算符连接,而 Python 中的 andor 在 Peewee 中是不支持的,此时我们需要使用 Peewee 封装好的运算符,如下:

逻辑符 含义 样例
& and Person.select().where((Person.Name == '张三') & (Person.Age == 30))
| or Person.select().where((Person.Name == '张三') \| (Person.Age == 30))
~ not Person.select().where(~Person.Name == '张三')

特别注意: 有多个条件时,每个条件 必须 用 () 括起来。

当条件全为 and 时,也可以用逗号分隔, getselect 中都可以:

Person.get(Person.Name == '张三', Person.Age == 30)
复制代码

六、支持的比较符

运算符 含义
== 等于
< 小于
<= 小于等于
> 大于
>= 大于等于
!= 不等于
<< x in y,其中 y 是列表或查询
>> x is y, 其中 y 可以是 None
% x like y
** x like y

注意:由于 SQLite 的 LIKE 操作默认情况下不区分大小写,因此 peewee 将使用 SQLite GLOB 操作进行区分大小写的搜索。glob 操作使用星号表示通配符,而不是通常的百分号。如果您正在使用 SQLite 并希望区分大小写的部分字符串匹配,请记住使用星号作为通配符。

解释一下,在 SQLite 中,如果希望 like 的时候区分大小写,可以这么写:

Person.select().where(Person.Remarks % 'a*')
复制代码

如果不希望区分大小写,这么写:

Person.select().where(Person.Remarks ** 'a%')
复制代码
Python:轻量级 ORM 框架 peewee 用法详解之——增删改查

以上所述就是小编给大家介绍的《Python:轻量级 ORM 框架 peewee 用法详解之——增删改查》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Go Web 编程

Go Web 编程

[新加坡]Sau Sheong Chang(郑兆雄) / 黄健宏 / 人民邮电出版社 / 2017-11-22 / 79

《Go Web 编程》原名《Go Web Programming》,原书由新加坡开发者郑兆雄(Sau Sheong Chang)创作、 Manning 出版社出版,人名邮电出版社引进了该书的中文版权,并将其交由黄健宏进行翻译。 《Go Web 编程》一书围绕一个网络论坛 作为例子,教授读者如何使用请求处理器、多路复用器、模板引擎、存储系统等核心组件去构建一个 Go Web 应用,然后在该应用......一起来看看 《Go Web 编程》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具