花 5 分鐘學習 wiki,然後進入實際的應用程序編程,Django 程序員很容易創建讓人迷惑、難於維護或低效的模型類。在本文中,了解如何避免一些常見的查詢錯誤、如何使用模型管理器來封裝複雜查詢以及如何充分利用 Django V1.1 強大的聚集特性。
在 Django 內,與資料庫的大多數交互都通過對象關係映射器(ORM),這個特性是 Django 與其他最新的 Web 框架(比如 Rails)所共有的。ORM 越來越受開發人員歡迎,因為 ORM 能夠自動化與資料庫的很多常見交互,而且會使用為人熟知的面向對象方式,而不是 SQL 語句。
Django 程序員可能會選擇繞過原生 ORM,而選擇流行的 SQLAlchemy 包,雖然 SQLAlchemy 十分強大,但是卻較難使用,而且需要更多的代碼行。雖然有些 Django 應用程序是使用 SQLAlchemy 而非原生 ORM 開發的,但是 Django 最吸引人的一些特性,比如其自動生成的管理界面,都要求使用 ORM。
|
本文著重闡釋了 Django ORM 的一些不為人熟知的特性,此外,本文還為 SQLAlchemy 的用戶提供了一些有關低效查詢生成的告誡,這對其編碼很有幫助。
本文中使用的軟體版本包括:
Django ORM 支持很多資料庫後端,但 sqlite3 最易於安裝,並且常常與操作系統捆綁。本文中的例子應該能與任何後端協作。要想獲得 Django 支持的資料庫的完整列表,請參見 參考資料。
避免 ORM 查詢生成中的常見陷阱
Django 的設計支持敏捷開發的風格,因此能快速進行原型化和實驗。在開始階段,最好不要過於擔心性能,而是要關注可讀性和實現的簡便性。
有時,發現性能問題並不需要太長時間。通常在初次用實際數據試用應用程序時,很容易發現性能問題。有時,若只包含幾個測試的測試套件的執行時間超過了 5 分鐘的界限,這就表明存在性能問題。有時,應用程序運行過慢,也表示性能問題的存在。所幸的是,現在已經有了一些很容易識別的模式,這些模式亦很容易修復。清單 1(應用程序的 models.py 文件)和 清單 2 給出了一個很常見的例子。
from django.db import models # Some kind of document, like a blog post or a wiki page class Document(models.Model): name = models.CharField(max_length=255) # A user-generated comment, such as found on a site like # Digg or Reddit class Comment(models.Model): document = models.ForeignKey(Document, related_name='comments') content = models.TextField() |
|
清單 2 顯示了如何以一種低效的方式訪問清單 1 中所設置的那些模型。
from examples.model import * import uuid # First create a lot of documents and assign them random names for i in range(0, 10000): Document.objects.create(name=str(uuid.uuid4())) # Get a selection of names back to be looked up later names = Document.objects.values_list('name', flat=True)[0:5000] # The really slow way to get back a list of Documents that # match these names documents = [] for name in names: documents.append(Document.objects.get(name=name)) |
這雖然是一個人為的示例,卻展示了一種非常常見的用例:給定一列標識符,從資料庫獲得對應於這些標識符的所有項目。
當使用內存中的 sqlite3 時,上述示例代碼的運行時間為 65 秒。如果是一個獨立於文件系統的資料庫,運行所花時間可能更長。不過,清單 3 中也有針對這個運行緩慢的查詢的一個補丁。與針對每個名稱值發出多個資料庫查詢相反,使用 fieldname__in 操作符來生成一個 SQL 查詢,如下所示:
SELECT * FROM model WHERE fieldname IN ('1', '2', ...) |
(所生成的實際查詢語法將會隨資料庫引擎而變化。)
from examples import models import uuid for i in range(0, 10000): Document.objects.create(name=str(uuid.uuid4())) names = Document.objects.values_list('name', flat=True)[0:5000] documents = list(Document.objects.filter(name__in=names)) |
上述代碼在 3 秒內即可執行。請注意此代碼會將查詢結果強制轉型為一個列表,以強制對此查詢求值。由於 Django 查詢會被延遲求值,因此,簡單的分配查詢結果並不會引起對資料庫的任何訪問,亦使對比無效。
習慣於編寫原始 SQL 的資料庫大師們會覺得本例十分直白,但是很多 Python 程序員並不具有資料庫背景。有時,程序員的開發習慣往往有悖於效率。清單 4 給出了改進清單 2 中的代碼的一種可能方式,這種方式是程序員很有可能選擇採用的,因為他們沒有意識到這是個陷阱。
for name in names: documents.append(get_document_by_name(name)) def get_document_by_name(name): return Document.objects.get(name=name)) |
表面上看,創建一個用來從資料庫檢索文檔的單獨方法似乎是個不錯的主意。但是這裡還有其他一些工作要做,例如在返回前向模型中添加數據。請注意,對於這個模型,進行重構形成獨立的方法看起來像是對代碼的改進。在開發之初就編寫單元測試並包括進一些針對大型數據集的測試可以幫助我們識別重構所導致的性能驟降。
用管理器模型封裝常見查詢
所有 Django 的開發人員都使用內置 Manager 類:表單 Model.objects.* 的所有方法,都會調用此類。這個基礎 Manager 類自動可用,並且提供常用的一些能夠返回 QuerySets 的方法(例如,all())、返回值的方法(例如,count())及返回 Model 實例的方法(例如, get_or_create())。
我們鼓勵 Django 的開發人員覆蓋這個基礎 Manager 類。為了說明此特性的用處,我們對這個示例應用程序進行了擴展,為它添加了一個新模型 Format,這個模型描述了此系統內文檔的格式。下面是一個示例。
from django.db import models class Document(models.Model): name = models.CharField(max_length=255) format = models.ForeignKey('Format') class Comment(models.Model): document = models.ForeignKey(Document, related_name='comments') content = models.TextField() class Format(models.Model): type = models.CharField(choices=( ('Text file', 'text'), ('ePub ebook', 'epub'), ('HTML file', 'html')), max_length=10) |
[火星人 ] 更好的 Django 模型 在 Django V1.1 內使用模型管理器已經有612次圍觀