[【Python 庫】輕量級 ORM 框架 peewee 用法詳解之——增刪改查]
說明:peewee 中有很多方法是延時執(zhí)行的吃衅,需要調用
execute()
方法使其執(zhí)行。下文中不再特意說明這個問題逸贾,大家看代碼宠互。
本文中代碼樣例所使用的 Person 模型如下:
class Person(Model): Name = CharField() Age = IntegerField() Birthday = DateTimeField() Remarks = CharField(null=True)
一浴讯、新增
1、create
Model.create
向數(shù)據(jù)庫中插入一條記錄,并返回一個新的實例辣吃。
p = Person.create(Name='張三', Age=30, Birthday=date(1990, 1, 1))
2动遭、save
語法:
save(force_insert=False, only=None)
參數(shù):
- force_insert:是否強制插入
- only(list):需要持久化的字段,當提供此參數(shù)時神得,只有提供的字段被持久化厘惦。
示例:
p1 = Person(Name='王五', Age=50, Birthday=date(1970, 1, 1))p1.save()
這里說的比較簡單,下面會詳細說明哩簿。
3宵蕉、insert
insert
只插入數(shù)據(jù)而不創(chuàng)建模型實例,返回新行的主鍵节榜。
Person.insert(Name='李四', Age=40, Birthday=date(1980, 1, 1)).execute()
4羡玛、insert_many
語法:
insert_many(rows, fields=None)
參數(shù):
- rows:元組或字典列表,要插入的數(shù)據(jù)
- fields(list):需要插入的字段名列表宗苍。
說明:
1稼稿、當 rows 傳遞的是字典列表時,fields 是不需要傳的讳窟,如果傳了让歼,那么,rows 中的字段在字典中必須存在丽啡,否則報錯谋右。如果沒有傳遞 fields 參數(shù),那么默認取所有字典的交集作為插入字段补箍。這個也好理解改执,比如一個字典的鍵是a、b坑雅、c
天梧,一個是b、c霞丧、d
,那么就取b冕香、c
作為需要插入的字段蛹尝。peewee 不會為缺失的字段做默認處理。
2悉尾、當 rows 傳遞的是元組列表時突那,必須指定 fields,并且 fields 中字段名的順序跟元組一致构眯。元組中值的數(shù)量必須大于等于 fields 中字段的數(shù)量愕难,一般建議是保持一致。
示例:
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()
對于批量操作,應該放在事務中執(zhí)行:
with db.atomic(): Person.insert_many(data, fields=fields).execute()
在使用批量插入時猫缭,如果是 SQLite葱弟,SQLite3 版本必須為 3.7.11.0 或更高版本才能利用批量插入API。此外猜丹,默認情況下芝加,SQLite 將 SQL 查詢中的綁定變量數(shù)限制為 999。
SQLite 中射窒,當批量插入的行數(shù)超過 999 時藏杖,就需要使用循環(huán)來將數(shù)據(jù)批量分組:
with db.atomic(): for idx in range(0, len(data), 100): Person.insert_many(data[idx: idx+100], fields=fields).execute()
Peewee 中帶有一個分塊輔助函數(shù) 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)
參數(shù):
- model_list (iterable):未保存的模型實例的列表或其他可迭代對象蝌麸。
- batch_size (int):每次批量插入的行數(shù)。如果未指定艾疟,則一次性全部插入来吩。
示例:
簡單來說,insert_many
使用字典或元組列表作為參數(shù)汉柒,而 model_list
使用模型實例列表作為參數(shù)误褪,就這區(qū)別。
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,執(zhí)行上述代碼之后正塌,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
就是自動添加了一個事務,然后一條條的插入鸠天,所以返回的模型實例中能獲取到主鍵讼育。
參數(shù)第一個是字典列表,第二個就是每多少條啟用一個事務稠集,大家可以把它改成 1 看下 SQL 語句就明白了奶段。
7、insert_from
使用 SELECT 查詢作為源 INSERT 數(shù)據(jù)剥纷。此 API 應用于 INSERT INTO … SELECT FROM … 形式的查詢痹籍。
語法:
insert_from(query, fields)
參數(shù):
- query:SELECT查詢用作數(shù)據(jù)源
- fields:要將數(shù)據(jù)插入的字段,此參數(shù)必須要的
示例:我們將 Person 表按原結構復制一個 Person2 表出來晦鞋,以做演示蹲缠。
data = Person.select(Person.Name, Person.Age, Person.Birthday)Person2.insert_from(data, ['Name', 'Age', 'Birthday']).execute()
注意: 因為是 INSERT INTO … SELECT FROM … 形式的棺克,所以數(shù)據(jù)源的列跟要插入的列必須保持一致。
二线定、刪除
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
直接執(zhí)行刪除了抹剩,不用調用execute()
方法。
參數(shù):
一般我都是先講參數(shù)再講示例的蓉坎,這次倒過來澳眷,示例其實很簡單,一看就明白蛉艾。但是這個參數(shù)缺需要好好講下钳踊。
這兩個參數(shù)都跟外鍵有關。我們修改一下測試用的模型勿侯。假設有這樣兩個模型拓瞪,一個人員,一個部門助琐,人員屬于部門祭埂。
class Department(Model): Name = CharField() class Meta: database = dbclass 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)# 執(zhí)行的 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)# 執(zhí)行的 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)# 執(zhí)行的 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)# 執(zhí)行的 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()
方法可以插入一條記錄,一旦模型實例具有主鍵纵诞,任何后續(xù)調用 save()
都將導致 UPDATE 而不是另一個 INSERT上祈。模型的主鍵不會改變。
p = Person(Name='王五', Age=50, Birthday=date(1970, 1, 1))p.save()print(p1.id)p.Remarks = 'abc'p.save()
這個例子浙芙,第一次執(zhí)行的 save
是 INSERT登刺,第二次是 UPDATE。
這里解釋一下嗡呼,
Person
這個模型纸俭,我并沒有指定主鍵,peewee 會自動增加一個名為 id 的自增列作為主鍵南窗。在執(zhí)行第一個save()
方法的時候揍很,主鍵沒值,所以執(zhí)行 INSERT万伤,save()
方法執(zhí)行之后窒悔,自增列的值就返回并賦給了模型實例,所以第二次調用save()
執(zhí)行的是 UPDATE敌买。
如果模型中一開始就用PrimaryKeyField
或primary_key
指定了主鍵简珠,那么save
執(zhí)行的永遠都是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()
這樣當然是可以實現(xiàn)的,但是這不僅速度慢缓淹,而且如果多個進程同時更新計數(shù)器哈打,它也容易受到競爭條件的影響塔逃。
我們可以用 update
方法來實現(xiàn)。
Person.update(Age=Person.Age+1).execute()
四料仗、查詢
1湾盗、get
Model.get()
方法檢索與給定查詢匹配的單個實例。
語法:
get(*query, **filters)
參數(shù):
- query:查詢條件
- filters:Mapping of field-name to value for Django-style filter. 我翻遍網(wǎng)上文章和官方文檔都沒找到這玩意怎么用立轧!
示例:
p1 = Person.get(Name='張三')
或者
p2 = Person.get(Person.Name == '李四')
當獲取的結果不存在時格粪,報 Model.DoesNotExist
異常。如果有多條記錄滿足條件氛改,則返回第一條帐萎。
2、get_or_none
如果當獲取的結果不存在時平窘,不想報錯吓肋,可以使用 Model.get_or_none()
方法,會返回 None
瑰艘,參數(shù)和 get
方法一致是鬼。
3、get_by_id
對于主鍵查找紫新,還可以使用快捷方法Model.get_by_id()
均蜜。
Person.get_by_id(1)
4、get_or_create
Peewee 有一個輔助方法來執(zhí)行“獲取/創(chuàng)建”類型的操作: Model.get_or_create()
首先嘗試檢索匹配的行芒率。如果失敗囤耳,將創(chuàng)建一個新行。
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)])
參數(shù):
get_or_create
的參數(shù)是 **kwargs
偶芍,其中 defaults 為非查詢條件的參數(shù)充择,剩余的為嘗試檢索匹配的條件,這個看執(zhí)行時的 SQL 語句就一目了然了匪蟀。對于“創(chuàng)建或獲取”類型邏輯椎麦,通常會依賴唯一 約束或主鍵來防止創(chuàng)建重復對象。但這并不是強制的材彪,比如例子中观挎,我以 Name
為條件,而 Name
并非主鍵段化。只是最好不要這樣做嘁捷。
返回值:
get_or_create
方法有兩個返回值,第一個是“獲取/創(chuàng)建”的模型實例显熏,第二個是是否新創(chuàng)建雄嚣。
5、select
使用 Model.select()
查詢獲取多條數(shù)據(jù)喘蟆。select
后可以添加 where
條件现诀,如果不加則查詢整個表夷磕。
語法:
select(*fields)
參數(shù):
- fields:需要查詢的字段,不傳時返回所有字段仔沿。傳遞方式如下例所示。
示例:
ps = Person.select(Person.Name, Person.Age).where(Person.Name == '張三')
select()
返回結果是一個 ModelSelect
對象尺棋,該對象可迭代封锉、索引、切片膘螟。當查詢不到結果時成福,不報錯,返回 None
荆残。并且 select()
結果是延時返回的奴艾。如果想立即執(zhí)行,可以調用 execute()
方法内斯。
注意:
where
中的條件不支持Name='張三'
這種寫法蕴潦,只能是Person.Name == '張三'
。
6俘闯、獲取記錄條數(shù) count 方法
使用 .count()
方法可以獲取記錄條數(shù)潭苞。
Person.select().count()
也許你會問,用 len()
方法可以嗎真朗?當然也是可以的此疹,但是是一種不可取的方法。
len(Person.select())
這兩者的實現(xiàn)方式天差地遠遮婶。用 count()
方法蝗碎,執(zhí)行的 SQL 語句是:
('SELECT COUNT(1) FROM (SELECT 1 FROM "person" AS "t1") AS "_wrapped"', [])
而用 len()
方法執(zhí)行的 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 中的 and
、or
在 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 時谜叹,也可以用逗號分隔匾寝,get 和 select 中都可以:
Person.get(Person.Name == '張三', Person.Age == 30)
六、支持的比較符
運算符 | 含義 |
---|---|
== | 等于 |
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
!= | 不等于 |
<< | x in y荷腊,其中 y 是列表或查詢 |
>> | x is y, 其中 y 可以是 None |
% | x like y |
** | x like y |
注意:由于 SQLite 的 LIKE 操作默認情況下不區(qū)分大小寫艳悔,因此 peewee 將使用 SQLite GLOB 操作進行區(qū)分大小寫的搜索。glob 操作使用星號表示通配符女仰,而不是通常的百分號猜年。如果您正在使用 SQLite 并希望區(qū)分大小寫的部分字符串匹配,請記住使用星號作為通配符疾忍。
解釋一下乔外,在 SQLite 中,如果希望 like 的時候區(qū)分大小寫一罩,可以這么寫:
Person.select().where(Person.Remarks % 'a*')
如果不希望區(qū)分大小寫杨幼,這么寫:
Person.select().where(Person.Remarks ** 'a%')
------------------------------------ END ------------------------------------