實體屬性參考資料

使用 Datastore 模式的 Firestore 支援各種屬性值的資料類型。其中包括:

  • 整數
  • 浮點數
  • 字串
  • 日期
  • 二進位資料

如需這些類型的完整清單,請參閱「屬性和值類型」一節。

屬性和值類型

與實體相關聯的資料值由一或多個「屬性」組成。每個屬性都有一個名稱及一或多個值。一個屬性可能會有多個類型的值,而兩個實體的相同屬性可能會有不同類型的值。屬性可能已建立索引或未建立索引 (排序或篩選屬性「P」的查詢將忽略「P」未建立索引的實體)。一個實體最多可有 20,000 個已建立索引的屬性。

支援的值類型如下:

當查詢的屬性具有混合類型的值時,Datastore 會根據內部表示法使用確定性排序:

  1. 空值
  2. 固定點數
    • 整數
    • 日期和時間
  3. 布林值
  4. 位元組序列
    • Unicode 字串
    • Blobstore 索引鍵
  5. 浮點數
  6. Datastore 索引鍵

長文字字串和長位元組字串不會建立索引,因此未定義排序。

屬性類型

NDB 支援以下屬性類型:

屬性類型 說明
IntegerProperty 64 位元帶正負號整數
FloatProperty 雙精確度浮點數
BooleanProperty 布林值
StringProperty Unicode 字串;最多可為 1500 個位元組,會建立索引
TextProperty Unicode 字串;無長度限制,不會建立索引
BlobProperty 未解譯的位元組字串:
如果您設定 indexed=True,長度上限為 1500 個位元組,且已編入索引;
如果 indexedFalse (預設值),長度不受限制,且未編入索引。
選用的關鍵字引數:compressed
DateTimeProperty 日期和時間 (請參閱日期和時間屬性)
DateProperty 日期 (請參閱日期和時間屬性)
TimeProperty 時間 (請參閱日期和時間屬性)
GeoPtProperty 地理位置。這是 ndb.GeoPt 物件。這個物件具有 latlon 屬性,兩者都是浮點值。您可以使用兩個浮點數建構此物件,例如 ndb.GeoPt(52.37, 4.88),或使用字串 ndb.GeoPt("52.37, 4.88")。(這實際上跟 db.GeoPt 是同一個類別)
KeyProperty Datastore 鍵
選用關鍵字引數:kind=kind,要求指派給此屬性的鍵一律具有指定的類型。可以是字串或 Model 子類別。
BlobKeyProperty Blobstore 鍵
對應至舊版資料庫 API 中的 BlobReferenceProperty,但屬性值為 BlobKey,而非 BlobInfo;您可以使用 BlobInfo(blobkey) 建構 BlobInfo
UserProperty 使用者物件。
StructuredProperty 在一個模型種類中包含另一個模型種類,依值列出 (請參閱結構化屬性)
LocalStructuredProperty 類似 StructuredProperty,但在磁碟上會顯示為不透明 blob,不會建立索引 (請參閱「結構化屬性」)。
選用的關鍵字引數:compressed
JsonProperty 值是可使用 Python 的 json 模組序列化的 Python 物件 (例如清單、字典或字串);資料儲存庫會將 JSON 序列化儲存為 Blob。預設不會建立索引。
選用的關鍵字引數:compressed
PickleProperty 值是可使用 Python 的 pickle 通訊協定序列化的 Python 物件 (例如清單、字典或字串);Datastore 會將 pickle 序列化做為 Blob 儲存。預設不會建立索引。
選用的關鍵字引數:compressed
GenericProperty 通用值
大多用於 Expando 類別,但也可直接使用。其類型可以是 intlongfloatboolstrunicodedatetimeKeyBlobKeyGeoPtUserNone 中的任何一種。
ComputedProperty 使用者定義函式從其他屬性計算出的值。(請參閱「運算屬性」)。

其中部分屬性有選用的關鍵字引數 compressed。如果資源有 compressed=True,則其資料會在磁碟上使用 gzip 壓縮。這類儲存空間較少,但需要 CPU 在寫入和讀取作業中進行編碼/解碼。

壓縮和解壓縮都是「延遲」作業;壓縮屬性值只會在您首次存取時解壓縮。如果在沒有存取壓縮屬性的情況下讀取並寫回包含壓縮屬性值的實體,將不會進行任何解壓縮和壓縮作業。在這種消極結構定義下也會進行內容快取,但 Memcache 一律會儲存壓縮屬性的壓縮值。

壓縮作業需要額外的 CPU 作業時間,因此最佳做法是:只有在資料因未經壓縮而過大時才使用壓縮屬性。請記住,gzip 壓縮作業通常無法用於圖片和其他媒體資料,這是因為這些格式已使用媒體專屬的壓縮演算法進行壓縮 (例如,圖片使用 JPEG)。

屬性選項

大多數的屬性類型都支援一些標準引數。第一個是可選的關鍵字位置引數,用於指定屬性的 Datastore 名稱。您可以使用這個屬性,在 Datastore 中為屬性指定與應用程式觀點不同的名稱。這項功能的常見用途是減少 Datastore 中的空間,讓 Datastore 使用縮寫的屬性名稱,而您的程式碼則使用較長、更有意義的名稱。例如:

class Employee(ndb.Model):
    full_name = ndb.StringProperty('n')
    retirement_age = ndb.IntegerProperty('r')

這對於重複屬性特別實用,因為每個實體都會有許多值。

此外,大部分屬性類型支援下列關鍵字引數:

引數 類型 預設 說明
indexed bool 一般為 True在 Datastore 的索引中加入屬性;如果是 False,則無法查詢值,但寫入速度會更快。並非所有資源類型都支援索引功能,因此將 indexed 設為 True 會失敗。
未編入索引的屬性比已編入索引的屬性需要較少的寫入作業。
repeated bool False 屬性值為包含基本類型值的 Python 清單 (請參閱重複屬性)。
無法與 required=Truedefault=True 合併使用。
required bool False 屬性必須有指定值。
default 屬性的基本類型 未明確指定時的預設屬性值。
choices 基本類型的值清單 None 選用的允許值清單。
validator 函式 None

用於驗證並且可能還會強制轉換該值的選用函式。

會以引數 (propvalue) 呼叫,並應傳回 (可能強制轉換的) 值或引發例外狀況。對強制轉換的值再次呼叫函式時,不應進一步修改該值。(例如,傳回 value.strip()value.lower() 是可以的,但 value + '$' 則不行)。您也可以傳回 None,代表「沒有變更」。另請參閱「寫入屬性子類別

verbose_name 字串None

在 jinja2 等網頁表單架構中使用的選用 HTML 標籤。

重複屬性

具有 repeated=True 的任何屬性即為「重複屬性」。這個屬性會採用基礎類型的值清單,而非單一值。舉例來說,使用 IntegerProperty(repeated=True) 定義的屬性值是整數清單。

資料儲存庫可能會為這類屬性顯示多個值。系統會為每個值建立個別的索引記錄。這會影響查詢語意;請參閱「 查詢重複屬性」一文的範例。

下列範例使用重複屬性:

class Article(ndb.Model):
    title = ndb.StringProperty()
    stars = ndb.IntegerProperty()
    tags = ndb.StringProperty(repeated=True)
...
article = Article(
    title='Python versus Ruby',
    stars=3,
    tags=['python', 'ruby'])
article.put()

這會建立含有以下內容的 Datastore 實體:

assert article.title == 'Python versus Ruby'
assert article.stars == 3
assert sorted(article.tags) == sorted(['python', 'ruby'])

查詢 tags 屬性時,這個實體會滿足 'python''ruby' 的查詢。

更新重複的屬性時,您可以為其指派新的清單,或在原地變更現有清單。指派新清單時,系統會立即驗證清單項目類型。無效的項目類型 (例如將 [1, 2] 指派給上述 art.tags) 會引發例外狀況。當您變異清單時,系統不會立即驗證變更。相反地,當您將實體寫入 Datastore 時,系統會驗證該值。

Datastore 會保留重複屬性中的清單項目順序,因此您可以為其排序指定一些意義。

日期和時間屬性

用於儲存日期與時間相關值的屬性類型有三種:

  • DateProperty
  • TimeProperty
  • DateTimeProperty

這些值屬於標準 Python datetime 模組的對應類別 (datetimedatetime)。這三者中最通用的是 DateTimeProperty,可同時表示日曆日期和時間;其他兩者偶爾可用於特殊用途,例如只需要日期 (例如生日) 或時間 (例如會議時間)。基於技術因素,DatePropertyTimePropertyDateTimeProperty 的子類別,但您不應依賴此繼承關係 (請注意,這與 datetime 模組本身定義的基礎類別之間的繼承關係不同)。

注意:App Engine 時鐘時間一律以世界標準時間 (UTC) 表示。如果您使用目前日期或時間 (datetime.datetime.now()) 做為值,或是在日期時間物件與 POSIX 時間戳記或時間元組之間轉換,就會用到這個屬性。不過,Datastore 不會儲存明確的時區資訊,因此如果您謹慎使用這些資訊,即可用於表示任何時區的本地時間 (如果您使用目前時間或轉換功能)。

這些屬性各有兩個額外的布林值關鍵字選項:

選項 說明
auto_now_add 將屬性設為實體建立時的日期/時間。您可以手動覆寫這個屬性。在實體更新時,屬性不會變更。針對該行為,請使用 auto_now
auto_now 在實體建立時和每次更新時,將屬性設為目前的日期/時間。

這些選項無法與 repeated=True 結合使用。這兩個屬性都預設為 False;如果兩者都設為 True,則 auto_now 的效力優先。您可以使用 auto_now_add=True 覆寫屬性值,但無法使用 auto_now=True 覆寫。實體寫入後,系統才會產生自動值,也就是說,這些選項不會提供動態預設值。(這些詳細資料與舊版 db API 不同)。

注意:如果交易在寫入具備 auto_now_add=True 的屬性時失敗並在之後重試,該交易會使用與初次嘗試時相同的時間值,而不會更新為重試的時間。如果交易永久失敗,屬性的值仍會在實體的記憶體內部副本中設定。

結構化屬性

您可以設定模型的屬性。舉例來說,您可以定義 Contact 模型類別,其中包含地址清單,每個地址都有內部結構。「結構化屬性」 (類型 StructuredProperty) 可讓您進行此操作;例如:

class Address(ndb.Model):
    type = ndb.StringProperty()  # E.g., 'home', 'work'
    street = ndb.StringProperty()
    city = ndb.StringProperty()
...
class Contact(ndb.Model):
    name = ndb.StringProperty()
    addresses = ndb.StructuredProperty(Address, repeated=True)
...
guido = Contact(
    name='Guido',
    addresses=[
        Address(
            type='home',
            city='Amsterdam'),
        Address(
            type='work',
            street='Spear St',
            city='SF')])

guido.put()

這會建立單一 Datastore 實體,並包含下列屬性:

assert guido.name == 'Guido'
addresses = guido.addresses
assert addresses[0].type == 'home'
assert addresses[1].type == 'work'
assert addresses[0].street is None
assert addresses[1].street == 'Spear St'
assert addresses[0].city == 'Amsterdam'
assert addresses[1].city == 'SF'

讀取這類實體會完全重建原始的 Contact 實體。雖然 Address 例項的定義使用與模型類別相同的語法,但它們並非完整的實體。在 Datastore 中沒有自己的鍵。無法獨立擷取屬於 Contact 實體的資料。不過,應用程式可以查詢個別欄位的值,請參閱「 篩選結構化資源值」。請注意,從 Datastore 的角度來看,address.typeaddress.streetaddress.city 會視為平行陣列,但 NDB 程式庫會隱藏這項資訊,並建構 Address 例項的對應清單。

您可以為結構化屬性指定常用的屬性選項 (indexed 除外)。在本例中,資料儲存庫名稱是第二個位置引數 (第一個是用於定義子結構的模型類別)。

如果不需要查詢子結構體的內部屬性,可以改用本機結構化屬性 (LocalStructuredProperty)。如果您在上述範例中將 StructuredProperty 替換為 LocalStructuredProperty,Python 程式碼的行為會相同,但資料儲存庫只會看到每個位址的半透明 blob。在範例中建立的 guido 實體會以以下方式儲存: name = 'Guido' address = <opaque blob for {'type': 'home', 'city': 'Amsterdam'}> address = <opaque blob for {'type': 'work', 'city': 'SF', 'street': 'Spear St'}>

這個實體可正確讀回。由於這類屬性一律不會建立索引,因此您無法查詢地址值。

注意:具有巢狀結構屬性的 StructuredProperty (無論是否為結構化屬性) 僅支援單層的重複屬性。StructuredProperty 可以重複使用,或重複使用巢狀資源,但兩者不能同時使用。解決方法是使用 LocalStructuredProperty,因為它沒有這項限制 (但不允許查詢其資源值)。

運算屬性

「運算屬性」 (ComputedProperty) 屬於唯讀屬性,其值是以應用程式提供的函式運算其他屬性值所得出。請注意,運算屬性僅支援一般屬性支援的類型!計算的值會寫入 Datastore,以便在 Datastore 檢視器中查詢及顯示,但實體從 Datastore 讀取時,系統會忽略已儲存的值;相反地,系統會在每次要求值時呼叫函式,重新計算值。例如:

class SomeEntity(ndb.Model):
    name = ndb.StringProperty()
    name_lower = ndb.ComputedProperty(lambda self: self.name.lower())
...
entity = SomeEntity(name='Nick')
entity.put()

這會儲存具有下列屬性值的實體:

assert entity.name == 'Nick'
assert entity.name_lower == 'nick'

如果我們將名稱變更為「Nickie」,並要求 name_lower 的值,系統會傳回「nickie」:

entity.name = 'Nick'
assert entity.name_lower == 'nick'
entity.name = 'Nickie'
assert entity.name_lower == 'nickie'

注意:如果應用程式查詢運算值,請使用 ComputedProperty。如果您只想在 Python 程式碼中使用衍生版本,請定義一般方法或使用 Python 的 @property 內建方法。

注意:如果您不是手動指定金鑰來建立模型,而是依賴 Datastore 自動產生實體的 ID,則在第一個 put() 中,ComputedProperty 將無法讀取 ID 欄位,因為需要先運算欄位才會產生 ID。如果您需要使用實體 ID 的 ComputedProperty,可以使用 allocate_ids 方法產生 ID 和金鑰,以便建立實體,這樣 ComputedProperty 就能在實體的第一次 put() 中參照該 ID。

Google Protocol RPC 訊息屬性

Google Protocol RPC 程式庫採用結構化資料的 Message 物件;它們可能代表 RPC 要求、回應或其他項目。NDB 提供 API,可將 Google Protocol RPC Message 物件儲存為實體屬性。假設您定義了 Message 子類別:

from protorpc import messages
...
class Note(messages.Message):
    text = messages.StringField(1, required=True)
    when = messages.IntegerField(2)

您可以使用 NDB 的 msgprop API,將 Note 物件儲存在 Datastore 中,做為實體屬性值。

from google.appengine.ext import ndb
from google.appengine.ext.ndb import msgprop
...
class NoteStore(ndb.Model):
    note = msgprop.MessageProperty(Note, indexed_fields=['when'])
    name = ndb.StringProperty()
...
my_note = Note(text='Excellent note', when=50)

ns = NoteStore(note=my_note, name='excellent')
key = ns.put()

new_notes = NoteStore.query(NoteStore.note.when >= 10).fetch()

如果您要查詢欄位名稱,則必須為這些欄位建立索引。您可以指定要透過 indexed_fields 參數至 MessageProperty 建立索引的欄位名稱清單。

MessageProperty 支援許多 (但非全部) 屬性選項。支援項目如下:

  • name
  • repeated
  • required
  • default
  • choices
  • validator
  • verbose_name

訊息屬性不支援 indexed 屬性選項,因此您無法為 Message 值建立索引。(您可以按照上述說明為訊息建立索引欄位)。

巢狀訊息 (使用 MessageField) 也適用:

class Notebook(messages.Message):
    notes = messages.MessageField(Note, 1, repeated=True)
...
class SignedStorableNotebook(ndb.Model):
    author = ndb.StringProperty()
    nb = msgprop.MessageProperty(
        Notebook, indexed_fields=['notes.text', 'notes.when'])

MessageProperty 有特殊的屬性選項 protocol,可指定訊息物件如何序列化至 Datastore。其值為 protorpc.remote.Protocols 類別使用的通訊協定名稱。支援的通訊協定名稱為 protobufprotojson;預設為 protobuf

msgprop 也定義了 EnumProperty,這是可用於在實體中儲存 protorpc.messages.Enum 值的屬性類型。範例:

class Color(messages.Enum):
    RED = 620
    GREEN = 495
    BLUE = 450
...
class Part(ndb.Model):
    name = ndb.StringProperty()
    color = msgprop.EnumProperty(Color, required=True)
...
p1 = Part(name='foo', color=Color.RED)
print p1.color  # prints "RED"

EnumProperty 會將值儲存為整數;事實上,EnumPropertyIntegerProperty 的子類別。這表示您可以重新命名列舉值,而無須修改已儲存的實體,但無法重新編號。

EnumProperty 支援下列屬性選項

  • name
  • indexed
  • repeated
  • required
  • default
  • choices
  • validator
  • verbose_name

關於 NDB 實體模型

NDB 實體模型可定義屬性。實體屬性有點類似 Python 類別的資料成員,是一種結構化的方式來保存資料;它們也類似資料庫結構中的欄位。

一般應用程式定義資料模型的方式,是定義一個繼承自 Model 的類別,並使用某些屬性類別屬性。例如,假設使用者要求系統 將文字從英文翻譯成法文


from google.appengine.ext import ndb
...
class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

在此範例中,usernameuseridemailAccount 的屬性。

還有其他幾種房源類型。有些類型可用於表示日期和時間,並提供方便的自動更新功能。

應用程式可指定屬性的選項來調整屬性的行為;這可簡化驗證、設定預設值,或變更查詢索引作業。

模型可包含更複雜的屬性。重複屬性類似清單。結構化屬性類似物件。唯讀運算屬性是透過函式定義,因此您可以輕鬆根據一或多個其他屬性定義屬性。Expando 模型則可動態定義屬性。