快速入門

本文檔簡要概述 Peewee 的主要功能。本指南將涵蓋:

注意

如果您想要更深入的內容,這裡有一個關於使用 peewee 和 Flask 框架建立「twitter」風格的 Web 應用程式的完整教學。在專案的 examples/ 資料夾中,您可以找到更多獨立的 Peewee 範例,例如一個部落格應用程式

強烈建議您開啟互動式 shell 工作階段並執行程式碼。這樣您就可以實際感受輸入查詢的過程。

模型定義

模型類別、欄位和模型實例都對應到資料庫概念

物件

對應到…

模型類別

資料庫表格

欄位實例

表格中的欄位

模型實例

資料庫表格中的列

當使用 peewee 開始專案時,通常最好從資料模型開始,定義一個或多個 Model 類別

from peewee import *

db = SqliteDatabase('people.db')

class Person(Model):
    name = CharField()
    birthday = DateField()

    class Meta:
        database = db # This model uses the "people.db" database.

注意

Peewee 會自動從類別名稱推斷資料庫表格名稱。您可以透過在內部 "Meta" 類別中指定 table_name 屬性(與 database 屬性並列)來覆寫預設名稱。若要瞭解更多關於 Peewee 如何產生表格名稱的資訊,請參考表格名稱 章節。

另請注意,我們將模型命名為 Person 而不是 People。這是一個您應該遵循的慣例 - 即使表格會包含多個人,我們總是使用單數形式來命名類別。

有許多適用於儲存各種資料類型的 欄位類型。Peewee 會處理 Pythonic 值與資料庫使用的值之間的轉換,因此您可以在程式碼中使用 Python 類型而無需擔心。

當我們使用外鍵關聯建立模型之間的關係時,事情會變得有趣起來。使用 peewee 很簡單:

class Pet(Model):
    owner = ForeignKeyField(Person, backref='pets')
    name = CharField()
    animal_type = CharField()

    class Meta:
        database = db # this model uses the "people.db" database

現在我們有了模型,讓我們連線到資料庫。雖然沒有必要明確開啟連線,但這是一個好習慣,因為它可以立即揭示資料庫連線的任何錯誤,而不是在執行第一個查詢之後的任意時間。當您完成時關閉連線也是一個好習慣 – 例如,Web 應用程式可能會在收到請求時開啟連線,並在傳送回應時關閉連線。

db.connect()

我們將從在資料庫中建立表格開始,這些表格將儲存我們的資料。這將建立具有適當欄位、索引、序列和外鍵約束的表格:

db.create_tables([Person, Pet])

儲存資料

讓我們開始在資料庫中填入一些人員。我們將使用 save()create() 方法來新增和更新人員的記錄。

from datetime import date
uncle_bob = Person(name='Bob', birthday=date(1960, 1, 15))
uncle_bob.save() # bob is now stored in the database
# Returns: 1

注意

當您呼叫 save() 時,會傳回修改的列數。

您也可以透過呼叫 create() 方法來新增人員,該方法會傳回模型實例:

grandma = Person.create(name='Grandma', birthday=date(1935, 3, 1))
herb = Person.create(name='Herb', birthday=date(1950, 5, 5))

若要更新列,請修改模型實例並呼叫 save() 來持久化變更。在這裡,我們將變更奶奶的名字,然後將變更儲存在資料庫中:

grandma.name = 'Grandma L.'
grandma.save()  # Update grandma's name in the database.
# Returns: 1

現在我們在資料庫中儲存了 3 個人。讓我們給他們一些寵物。奶奶不喜歡家裡有動物,所以她不會有任何寵物,但 Herb 是一個動物愛好者:

bob_kitty = Pet.create(owner=uncle_bob, name='Kitty', animal_type='cat')
herb_fido = Pet.create(owner=herb, name='Fido', animal_type='dog')
herb_mittens = Pet.create(owner=herb, name='Mittens', animal_type='cat')
herb_mittens_jr = Pet.create(owner=herb, name='Mittens Jr', animal_type='cat')

在漫長的一生後,Mittens 生病並去世了。我們需要將他從資料庫中移除:

herb_mittens.delete_instance() # he had a great life
# Returns: 1

注意

delete_instance() 的傳回值是從資料庫中移除的列數。

叔叔 Bob 認為 Herb 家裡有太多動物死亡,所以他收養了 Fido:

herb_fido.owner = uncle_bob
herb_fido.save()

檢索資料

我們資料庫的真正優勢在於它如何允許我們透過查詢來檢索資料。關聯式資料庫非常適合進行臨時查詢。

取得單筆記錄

讓我們從資料庫中檢索奶奶的記錄。若要從資料庫中取得單筆記錄,請使用 Select.get()

grandma = Person.select().where(Person.name == 'Grandma L.').get()

我們也可以使用等效的簡寫 Model.get()

grandma = Person.get(Person.name == 'Grandma L.')

記錄列表

讓我們列出資料庫中的所有人:

for person in Person.select():
    print(person.name)

# prints:
# Bob
# Grandma L.
# Herb

讓我們列出所有貓和牠們主人的名字:

query = Pet.select().where(Pet.animal_type == 'cat')
for pet in query:
    print(pet.name, pet.owner.name)

# prints:
# Kitty Bob
# Mittens Jr Herb

注意

先前的查詢有一個很大的問題:因為我們正在存取 pet.owner.name,而且我們沒有在原始查詢中選取此關聯,所以 peewee 必須執行額外的查詢來檢索寵物的主人。此行為稱為 N+1,通常應避免。

有關使用關聯和聯結的深入指南,請參考關聯與聯結文件。

我們可以透過選取 PetPerson,並新增聯結來避免額外的查詢。

query = (Pet
         .select(Pet, Person)
         .join(Person)
         .where(Pet.animal_type == 'cat'))

for pet in query:
    print(pet.name, pet.owner.name)

# prints:
# Kitty Bob
# Mittens Jr Herb

讓我們取得 Bob 擁有的所有寵物:

for pet in Pet.select().join(Person).where(Person.name == 'Bob'):
    print(pet.name)

# prints:
# Kitty
# Fido

我們可以在這裡做另一件很酷的事情來取得 Bob 的寵物。由於我們已經有一個物件來代表 Bob,我們可以改為執行此操作:

for pet in Pet.select().where(Pet.owner == uncle_bob):
    print(pet.name)

排序

讓我們透過新增 order_by() 子句來確保這些資料依字母順序排序:

for pet in Pet.select().where(Pet.owner == uncle_bob).order_by(Pet.name):
    print(pet.name)

# prints:
# Fido
# Kitty

讓我們現在列出所有人,從最年輕到最年長:

for person in Person.select().order_by(Person.birthday.desc()):
    print(person.name, person.birthday)

# prints:
# Bob 1960-01-15
# Herb 1950-05-05
# Grandma L. 1935-03-01

組合篩選條件

Peewee 支援任意巢狀的運算式。讓我們取得所有生日是以下情況的人:

  • 1940 年之前(奶奶)

  • 1959 年之後(Bob)

d1940 = date(1940, 1, 1)
d1960 = date(1960, 1, 1)
query = (Person
         .select()
         .where((Person.birthday < d1940) | (Person.birthday > d1960)))

for person in query:
    print(person.name, person.birthday)

# prints:
# Bob 1960-01-15
# Grandma L. 1935-03-01

現在讓我們做相反的事情。生日在 1940 年到 1960 年之間(包括這兩年)的人:

query = (Person
         .select()
         .where(Person.birthday.between(d1940, d1960)))

for person in query:
    print(person.name, person.birthday)

# prints:
# Herb 1950-05-05

聚合與預取

現在讓我們列出所有人,以及他們擁有多少隻寵物:

for person in Person.select():
    print(person.name, person.pets.count(), 'pets')

# prints:
# Bob 2 pets
# Grandma L. 0 pets
# Herb 1 pets

我們再次遇到了 N+1 查詢行為的典型範例。在這種情況下,我們正在為原始 SELECT 傳回的每個 Person 執行額外的查詢!我們可以透過執行 JOIN 並使用 SQL 函數來彙總結果來避免這種情況。

query = (Person
         .select(Person, fn.COUNT(Pet.id).alias('pet_count'))
         .join(Pet, JOIN.LEFT_OUTER)  # include people without pets.
         .group_by(Person)
         .order_by(Person.name))

for person in query:
    # "pet_count" becomes an attribute on the returned model instances.
    print(person.name, person.pet_count, 'pets')

# prints:
# Bob 2 pets
# Grandma L. 0 pets
# Herb 1 pets

注意

Peewee 提供了一個神奇的輔助函數 fn(),可用於呼叫任何 SQL 函數。在上面的範例中,fn.COUNT(Pet.id).alias('pet_count') 會被轉換為 COUNT(pet.id) AS pet_count

現在讓我們列出所有人以及他們所有寵物的名字。正如您可能已經猜到的,如果不小心,這很容易變成另一種 N+1 的情況。

在深入研究程式碼之前,請考慮此範例與先前我們列出所有寵物及其主人姓名範例的不同之處。一個寵物只能有一個主人,因此當我們從 Pet 執行聯結到 Person 時,總會有一個單一的匹配項。當我們從 Person 聯結到 Pet 時,情況就不同了,因為一個人可能沒有寵物,也可能有多隻寵物。因為我們使用的是關聯式資料庫,如果我們從 Person 執行聯結到 Pet,那麼每個擁有多隻寵物的人都會重複,每隻寵物重複一次。

它看起來會像這樣:

query = (Person
         .select(Person, Pet)
         .join(Pet, JOIN.LEFT_OUTER)
         .order_by(Person.name, Pet.name))
for person in query:
    # We need to check if they have a pet instance attached, since not all
    # people have pets.
    if hasattr(person, 'pet'):
        print(person.name, person.pet.name)
    else:
        print(person.name, 'no pets')

# prints:
# Bob Fido
# Bob Kitty
# Grandma L. no pets
# Herb Mittens Jr

通常這種重複是不受歡迎的。為了適應列出人員並附加該人員寵物的列表的更常見(且直觀)的工作流程,我們可以使用一個名為 prefetch() 的特殊方法。

query = Person.select().order_by(Person.name).prefetch(Pet)
for person in query:
    print(person.name)
    for pet in person.pets:
        print('  *', pet.name)

# prints:
# Bob
#   * Kitty
#   * Fido
# Grandma L.
# Herb
#   * Mittens Jr

SQL 函數

最後一個查詢。這將使用 SQL 函數來尋找所有名字以大寫或小寫 G 開頭的人:

expression = fn.Lower(fn.Substr(Person.name, 1, 1)) == 'g'
for person in Person.select().where(expression):
    print(person.name)

# prints:
# Grandma L.

這只是基本知識!您可以使查詢盡可能複雜。請查看查詢的文件以取得更多資訊。

資料庫

我們已經完成資料庫的操作了,讓我們關閉連線:

db.close()

在實際應用中,有一些關於如何管理資料庫連線生命週期的既定模式。例如,一個網站應用程式通常會在請求開始時開啟連線,並在產生回應後關閉連線。 連線池 可以幫助消除與啟動成本相關的延遲。

若要了解如何設定您的資料庫,請參閱 資料庫 文件,其中提供了許多範例。Peewee 也支援 在執行時配置資料庫 以及隨時設定或變更資料庫。

使用現有資料庫

如果您已經有一個資料庫,您可以使用 pwiz,一個模型產生器 自動產生 peewee 模型。例如,如果我有一個名為charles_blog的 postgresql 資料庫,我可能會執行

python -m pwiz -e postgresql charles_blog > blog_models.py

下一步是什麼?

快速入門就到這裡結束。如果您想查看一個完整的網站應用程式,請查看 範例應用程式