My favorites | 中文(繁體) | Sign in
英文版或許有比此中譯版新的內容

實體和模型

資料存放區具有金鑰和一組屬性。應用程式使用 Datastore API (資料存放區 API) 來定義資料模型,以及建立這些要儲存為實體之模型的實例。模型提供 API 所建立之實體的常見架構,而且可以定義驗證屬性值的規則。

Model 介面

應用程式描述與模型一起使用的資料種類。模型是一種 Python 類別,它繼承自 Model 類別。模型類別定義資料存放區實體的新種類,以及該種類必須要有的屬性。

Model 屬性使用模型類別上的類別屬性 (attribute) 來定義。每個類別屬性 (attribute) 是 Property 類別之子類別的實例,通常是一個提供的屬性類別。屬性實例帶有屬性的設定 (例如實例是否一定要帶有該屬性才是有效的),或者帶有實例使用的預設值 (若未提供)。

from google.appengine.ext import db

class Pet(db.Model):
  name = db.StringProperty(required=True)
  type = db.StringProperty(required=True, choices=set(["cat", "dog", "bird"]))
  birthdate = db.DateProperty()
  weight_in_pounds = db.IntegerProperty()
  spayed_or_neutered = db.BooleanProperty()
  owner = db.UserProperty(required=True)

藉由相應模型類別的實體,一種定義之實體種類的實體會呈現在 API 中。 應用程式可以透過類別的建構函式來建立新實體。應用程式會使用實例的屬性 (attribute) 來存取和操作實體的屬性。模型實例建構函式接受屬性的初始值做為 keyword 引數。

from google.appengine.api import users

pet = Pet(name="Fluffy",
          type="cat",
          owner=users.get_current_user())
pet.weight_in_pounds = 24

注意:模型類別的屬性 (attribute) 是模型屬性的設定,而這些設定的值是 Property 實例。模型實例的屬性 (attribute) 是實際的屬性值,其值是 Property 類別接受的類型。

Model 類別使用 Property 實例來驗證指派給模型實例屬性 (attribute) 的值。模型實例一開始建構時,以及實例屬性 (attribute) 指派新值時,會驗證 Property 值。這樣可以確定屬性不會具有無效值。

因為驗證是發生在建構實例時,任何設定為必要的屬性必須在建構函式中初始化。在此範例中,nametype 以及 owner 都是必要值,所以必須在建構函式中指定其初始值。weight_in_pounds 不是模型所必需,所以一開始的時候未指派,而是稍後才進行指派。

使用建構函式建立的模型實例,要在第一次「放置」之後,才會存在於資料存放區。請參閱建立、取得和刪除資料

注意:針對所有 Python 類別屬性 (attribute) 而言,模型屬性設定會在指令碼或模型第一次匯入時就初始化。因為「應用服務引擎」快取會在要求之間匯入模組,模組設定可能在某位使用者要求的期間進行初始化,並於另一位使用者要求的期間重複使用。請勿將模組屬性設定 (例如預設值)以特定要求或目前使用者的資料進行初始化。請參閱 應用程式快取以取得詳細資訊。

Expando 模型

使用 Model 類別定義的模型,建立一組固定屬性。該類別的每個實例都必須具備此屬性 (可能是預設值)。這對模型資料物件是很有用的方式,但是資料存放區不會要求指定種類的每個實體都具有同一組的屬性。

有時候,實體具有的屬性不必像其他相同種類實體的屬性,反而較為方便。這樣的實體是由「expando」模型呈現在資料存放區 API。expando 模型類別是 Expando 超級類別下的子類別。任何指派給 expando 模型實例的屬性 (attribute) 值,都會使用該屬性 (attribute) 的名稱,成為資料存放區實體的屬性。這些屬性稱為動態屬性。使用類別屬性 (attribute) 中的 Property 類別實例所定義的屬性,則為固定屬性

expando 模型可以同時具有固定和動態屬性。model 類別只會以固定屬性的 Property 設定物件,來設定類別屬性 (attribute)。當應用程式指派其值時,會建立動態屬性。

class Person(db.Expando):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  hobbies = db.StringListProperty()

p = Person(first_name="Albert", last_name="Johnson")
p.hobbies = ["chess", "travel"]

p.chess_elo_rating = 1350

p.travel_countries_visited = ["Spain", "Italy", "USA", "Brazil"]
p.travel_trip_count = 13

因為動態屬性不具有模型屬性定義,所以不會驗證動態屬性。任何動態屬性可以具有資料存放區基礎類型任何值,包括 None。相同種類的兩個實體可以具有相同動態屬性的不同類型值,而且一個可以不設定屬性而另一個有設定。

與固定屬性不同的是,動態屬性不需要存在。具有 None 值的動態屬性與不存在的動態屬性不同。若 expando 模型實例的某屬性不具有屬性 (attribute),則相應的資料實體不會具備該屬性。只要刪除屬性 (attribute),就會刪除動態屬性。

del p.chess_elo_rating

在篩選器中使用動態屬性的查詢,只會傳回與查詢中使用相同值類型的屬性值。同樣地,查詢只會傳回已設定該屬性的實體。

p1 = Person()
p1.favorite = 42
p1.put()

p2 = Person()
p2.favorite = "blue"
p2.put()

p3 = Person()
p3.put()

people = db.GqlQuery("SELECT * FROM Person WHERE favorite < :1", 50)
# people has p1, but not p2 or p3

people = db.GqlQuery("SELECT * FROM Person WHERE favorite > :1", 50)
# people has no results

Expando 類別是 Model 類別的子類別,並繼承其所有方法。

多型態模型

Python API 包括另一種資料模型類別,可讓您定義類別的階層,以及執行可傳回特定類別或其任一子類別之實體的查詢。這種模型查詢稱為「多型態」的原因,是它們允許一種類別的實例成為父系類別查詢的結果

下列範例定義 Contact 類別,以及 Contact 的子類別 PersonCompany 類別:

from google.appengine.ext import db
from google.appengine.ext.db import polymodel

class Contact(polymodel.PolyModel):
  phone_number = db.PhoneNumberProperty()
  address = db.PostalAddressProperty()

class Person(Contact):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  mobile_number = db.PhoneNumberProperty()

class Company(Contact):
  name = db.StringProperty()
  fax_number = db.PhoneNumberProperty()

此模型會確保所有 Person 實體和所有 Company 實體具備 phone_numberaddress 屬性,且查詢 Contact 實體可傳回 PersonCompany 實體。只有 Person 實體具備 mobile_number 屬性。

子類別和其他模型類別一樣都可以被具現化:

p = Person(phone_number='1-206-555-9234',
           address='123 First Ave., Seattle, WA, 98101',
           first_name='Alfred',
           last_name='Smith',
           mobile_number='1-206-555-0117')
p.put()

c = Company(phone_number='1-503-555-9123',
            address='P.O. Box 98765, Salem, OR, 97301',
            name='Data Solutions, LLC',
            fax_number='1-503-555-6622')
c.put()

查詢 Contact 實體可傳回 ContactPersonCompany 的例項。下列程式碼會列印上方建立之兩個實體的資訊:

for contact in Contact.all():
  print 'Phone: %s\nAddress: %s\n\n'
        % (contact.phone,
           contact.address))

查詢 Company 實體可傳回 Company 例項:

for company in Company.all()
  # ...

如需如何使用多型態模型以及如何加以實作的詳細資料,請參閱「PolyModel 類別」。

屬性和類型

資料存放區支援實體屬性的固定值類型集,包括 Unicode 字串、整數、浮點數、日期、實體金鑰、字元字串 (blob) 以及各種 GData 類型。每一種資料存放區值類型具有 google.appengine.ext.db 模組提供之相應的 Property 類別。

類型和屬性類別描述所有支援的值類型以及其相應的 Property 類別。幾種特殊值類型描述如下。

字串與 Blob

資料存放區支援兩種儲存字串的值類型:長度最長可達 500 個位元組的短文字字串,以及長度最長可達 1 MB 的長文字字串。短字串可以進行索引,可以用於查詢篩選條件以及排序順序。長字串無法進行索引,而且無法用於篩選條件或排序順序。

短字串值可以是 unicode 值或 str 值。若值為 str,則會假設編碼為 'ascii'。若要為 str 值指定不同的編碼,您可以將它轉換為 unicode 值 (使用 unicode() 類型建構函式,它將 str 和編碼的名稱當做引數)。短字串可以使用 StringProperty 類別來模型化。

class MyModel(db.Model):
  string = db.StringProperty()

obj = MyModel()

# Python Unicode literal syntax fully describes characters in a text string.
obj.string = u"kittens"

# unicode() converts a byte string to a Unicode value using the named codec.
obj.string = unicode("kittens", "latin-1")

# A byte string is assumed to be text encoded as ASCII (the 'ascii' codec).
obj.string = "kittens"

# Short string properties can be used in query filters.
results = db.GqlQuery("SELECT * FROM MyModel WHERE string = :1", u"kittens")

長字串值是由 db.Text 實例所代表。其建構函式接受 unicode 值或 str 值,以及 str 中使用的編碼名稱。長字串可以使用 TextProperty 類別來模型化。

class MyModel(db.Model):
  text = db.TextProperty()

obj = MyModel()

# Text() can take a Unicode value.
obj.text = db.Text(u"lots of kittens")

# Text() can take a byte string and the name of an encoding.
obj.text = db.Text("lots of kittens", "latin-1")

# If no encoding is specified, a byte string is assumed to be ASCII text.
obj.text = db.Text("lots of kittens")

# Text properties can store large values.
obj.text = db.Text(open("a_tale_of_two_cities.txt").read(), "utf-8")

資料存放區也對非文字字元組字串提供兩種類似的支援類型:db.ByteStringdb.Blob。這些值為未經處理的位元祖,所以不會被視為編碼的文字 (如 UTF-8) 來處理。

就像 strunicode 值一樣,db.ByteString 值會建立索引,並以 500 個字元為限制。ByteString 實例代表短字串位元組,並接受 str 值做為其建構函式的引數。位元組字串使用 ByteStringProperty 類別來模型化。

就像 db.Text 一樣,db.Blob 值最常可為 1 MB,但未建立索引,且無法用於查詢篩選器和排序順序中。db.Blob 類別接受 str 值做為其建構函式的引數。Blob 會使用 BlobProperty 類別來模型化。

class MyModel(db.Model):
  blob = db.BlobProperty()

obj = MyModel()

obj.blob = db.Blob(open("image.png").read())

清單

可以具有多個值的屬性,在資料存放區 API 中呈現為 Python list。清單包含的值,可以是資料存放區支援的任何值類型。單一清單屬性甚至具有不同類型的值。順序受到保留,因此當查詢和 get() 傳回實體時,清單屬性值的順序會與它們的儲存順序相同。

ListProperty 類別的模型為清單,並且強制清單中的所有值必須是相同的指定類型。為了便利起見,程式庫也提供 StringListProperty,類似於 ListProperty(basestring)

class MyModel(db.Model):
  numbers = db.ListProperty(long)

obj = MyModel()
obj.numbers = [2, 4, 6, 8, 10]

obj.numbers = ["hello"]  # ERROR: MyModel.numbers must be a list of longs.

清單屬性上的查詢篩選器,會針對清單的成員測試指定值。若清單中至少有一個成員符合條件,則該條件為 true。

# Get all entities where numbers contains a 6.
results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers = 6")

# Get all entities where numbers contains at least one element less than 10.
results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers < 10")

查詢篩選器只能在清單成員上操作。在一個查詢篩選器中,無法測試兩個清單的相似性。

資料存放區在內部會將清單屬性值呈現為該屬性的多個值。若清單屬性值為空白清單,則該屬性在資料存放區中不會有會任何呈現。 資料存放區 API 針對靜態屬性 (具有 ListProperty) 以及動態屬性對這種情況的處理方式不同:

  • 靜態 ListProperty 可以指派空白清單為值。屬性不會存在於資料存放區,但是模型實例的行為就像是值位於空白清單中一樣。靜態 ListProperty 不能具有 None 值。
  • 具有 list 值的動態屬性不能指派空白清單值。不過,它可以具有 None 值,而且可以使用 del 將它刪除。

ListProperty 模型會測試加入清單的值是否為正確的類型,若類型不正確,則會發生 BadValueError。即使之前儲存的實體已擷取並載入模型中,仍會發生此測試 (而且可能會失敗)。因為 str 值會在儲存之前轉換為 unicode 值 (ASCII 文字),ListProperty(str) 會視為 ListProperty(basestring),Python 資料類型可接受 strunicode 值。您也可以使用 StringListProperty() 達到此目的。

針對存放非文字字元組字串,使用 db.Blob 值。blob 字串的位元組儲存和抓取時,會受到保留。將可以將 blob 清單的屬性宣告為 ListProperty(db.Blob)

清單屬性不會以一般方式與排序順序互動。請參閱「查詢和索引:擁有多個值的排序順序和屬性」以取得詳細資料。

參考資料

屬性值可以包含其他實體的金鑰。該值為 Key 實例。

ReferenceProperty 類別的模型為金鑰值,並且強制所有值必須參考相同指定種類的實體。為了便利起見,程式庫也提供 SelfReferenceProperty,等同於參考相同種類、具有屬性實體的 ReferenceProperty。

將模型實例指派給 ReferenceProperty 屬性時,會自動使用其金鑰做為值。

class FirstModel(db.Model):
  prop = db.IntegerProperty()

class SecondModel(db.Model):
  reference = db.ReferenceProperty(FirstModel)

obj1 = FirstModel()
obj1.prop = 42
obj1.put()

obj2 = SecondModel()

# A reference value is the key of another entity.
obj2.reference = obj1.key()

# Assigning a model instance to a property uses the entity's key as the value.
obj2.reference = obj1
obj2.put()

ReferenceProperty 屬性值可以當做被參考實體的模型實例來使用。若參考的實體不在記憶體中,使用其屬性做為實例時,會自動從資料存放區擷取該實體。

obj2.reference.prop = 999
obj2.reference.put()

results = db.GqlQuery("SELECT * FROM SecondModel")
another_obj = results.fetch(1)[0]
v = another_obj.reference.prop

刪除實體時,其金鑰若為參考屬性的值,則不會變更參考屬性。參考屬性值可以是不再有效的金鑰。若應用程式預期參考可能無效,則應用程式可以使用 if 陳述式來測試物件是否存在:

obj1 = obj2.reference

if not obj1:
  # Referenced entity was deleted.

ReferenceProperty 有另一個好用的功能:反向參考。當模型具有另一個模型的 ReferenceProperty 時,每個參考的實體都會取得屬性,此屬性值為 Query (會傳回參考它的第一個模型的所有實體)。

# To fetch and iterate over every SecondModel entity that refers to the
# FirstModel instance obj1:
for obj in obj1.secondmodel_set:
  # ...

反向參考的預設名稱為 modelname_set (模型類別的名稱為小寫字母,並在結尾加上 "_set"),而且可以使用 ReferenceProperty 建構函式的 collection_name 引數來進行調整。

若您有多個 ReferenceProperty 值參考相同的模型類別,則反向參考屬性的預設建構函式會引發錯誤:

class FirstModel(db.Model):
  prop = db.IntegerProperty()

# This class raises a DuplicatePropertyError with the message
# "Class Firstmodel already has property secondmodel_set"
class SecondModel(db.Model):
  reference_one = db.ReferenceProperty(FirstModel)
  reference_two = db.ReferenceProperty(FirstModel)

若要避免此錯誤,您必須明確地設定 collection_name 引數:

class FirstModel(db.Model):
  prop = db.IntegerProperty()

# This class runs fine
class SecondModel(db.Model):
  reference_one = db.ReferenceProperty(FirstModel,
      collection_name="secondmodel_reference_one_set")
  reference_two = db.ReferenceProperty(FirstModel,
      collection_name="secondmodel_reference_two_set") 

使用 ReferenceProperty 模型屬性類別時,才能使用模型實例的自動參考和解除參考、類型檢查以及反向參考。儲存為 Expando 動態屬性或 ListProperty 值的金鑰不具有這些功能。

屬性名稱

資料存放區將所有屬性名稱的開頭和結尾都保留為底線字元 (__*__)。應用程式無法建立這種名稱的屬性。

在 Python API 中,會忽略名稱開頭為底線 (_) 之模型實例的屬性 (attribute),而且這種屬性 (attribute) 不會儲存至資料存放區實體。這樣可以將值儲存在模型實例中,供您在內部暫時使用,而不會影響到實體儲存的資料。

因為 Python API 預設會使用模型實例的屬性 (attribute) 做為屬性名稱,因此實例方法已經使用的任何屬性,都不能直接做為 property attribute 名稱。同樣地,模型建構函式之關鍵字引數使用的任何名稱,也都不能做為 property attribute 名稱使用。請參閱保留的屬性名稱清單

但資料存放區本身則允許這些名稱。若應用程式需要資料存放區實體的屬性名稱,類似於 Python API 所保留的文字,則應用程式可以使用固定屬性,並將 name 引數傳送給 Property 類別建構函式。請參閱 Property 類別建構函式

class MyModel(db.Model):
  obj_key = db.StringProperty(name="key")