[【Python 庫】輕量級 ORM 框架 peewee 用法詳解之——增刪改查]

[【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í)行的 saveINSERT登刺,第二次是 UPDATE

這里解釋一下嗡呼,Person 這個模型纸俭,我并沒有指定主鍵,peewee 會自動增加一個名為 id 的自增列作為主鍵南窗。在執(zhí)行第一個 save() 方法的時候揍很,主鍵沒值,所以執(zhí)行 INSERT万伤,save() 方法執(zhí)行之后窒悔,自增列的值就返回并賦給了模型實例,所以第二次調用 save() 執(zhí)行的是 UPDATE敌买。
如果模型中一開始就用 PrimaryKeyFieldprimary_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 中的 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 操作默認情況下不區(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 ------------------------------------

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市聂渊,隨后出現(xiàn)的幾起案子差购,更是在濱河造成了極大的恐慌,老刑警劉巖歧沪,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歹撒,死亡現(xiàn)場離奇詭異,居然都是意外死亡诊胞,警方通過查閱死者的電腦和手機暖夭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撵孤,“玉大人迈着,你說我怎么就攤上這事⌒奥耄” “怎么了裕菠?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長闭专。 經(jīng)常有香客問我奴潘,道長,這世上最難降的妖魔是什么影钉? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任画髓,我火速辦了婚禮,結果婚禮上平委,老公的妹妹穿的比我還像新娘奈虾。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布肉微。 她就那樣靜靜地躺著匾鸥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碉纳。 梳的紋絲不亂的頭發(fā)上勿负,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音劳曹,去河邊找鬼笆环。 笑死,一個胖子當著我的面吹牛厚者,可吹牛的內容都是我干的。 我是一名探鬼主播迫吐,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼库菲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了志膀?” 一聲冷哼從身側響起熙宇,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎溉浙,沒想到半個月后烫止,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡戳稽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年馆蠕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惊奇。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡互躬,死狀恐怖,靈堂內的尸體忽然破棺而出颂郎,到底是詐尸還是另有隱情吼渡,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布乓序,位于F島的核電站寺酪,受9級特大地震影響,放射性物質發(fā)生泄漏替劈。R本人自食惡果不足惜寄雀,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抬纸。 院中可真熱鬧咙俩,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脖阵,卻和暖如春皂股,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背命黔。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工呜呐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悍募。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓蘑辑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坠宴。 傳聞我的和親對象是個殘疾皇子洋魂,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內容