Python:輕量級 ORM 框架 peewee 用法詳解

說明: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(199011))

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(198011)).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([
    ('張三'30date(199011)),
    ('李四'40date(198011)),
    ('王五'50date(197011))
],
    ['Name''Age''Birthday']
).execute()

Person.insert_many([
    {'Name''張三''Age'30'Birthday'date(199011)},
    {'Name''李四''Age'40'Birthday'date(198011)},
    {'Name''王五''Age'50'Birthday'date(197011)}
]
).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(0len(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(199011)),
        Person(Name='李四~', Age=40, Birthday=date(198011))]
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(199011)},
             {'Name''李四''Age'40'Birthday'date(198011)},
             {'Name''王五''Age'50'Birthday'date(197011)}]

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(197011))
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(1FROM (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%')
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章