Django 源碼小剖: Django ORM 查詢管理器

ORM 查詢管理器

對於 ORM 定義: 對象關係映射, Object Relational Mapping, ORM, 是一種程序設計技術,用於實現面向對象編程語言裏不一樣類型系統的數據之間的轉換。從效果上說,它實際上是建立了一個可在編程語言裏使用的「虛擬對象數據庫」。ORM 能大大簡化並抽象數據庫的操做.python

假設 django 的一個工程中包含一個名爲 Book 的模塊(model), 在 views.py 的函數中可能會寫出查詢語句:git

# views.py
def index(request):
    book_set = Book.objects.filter(id=1)
    或者
    book_set = Book.objects.all()
    ......

ORM 的做用就是這樣, 並不須要寫更復雜的 SQL 語句, 全部的的事情都被 ORM 代勞了.github

上面中, Book 其實是一個 Model 實例, 但先是從 Book.objects 開始提及. Book.objects 其實是一個 Manager 類實例, 每一個 Model 都會有一個, 用戶的查詢操做幾乎是從這裏開始的. 萬萬能夠將 Model 實例理解爲關係表中的一個表項數據, 而 Manager 實例能夠理解數據庫查詢的入口.數據庫

實際上, 不管如何都在 Model 類的源碼中找到任何 objects 屬性的字眼, 所以它確定是在某個時間點上增長的. 能夠在 django.db.models.manager.py 中找到下面的函數:django

這個函數確保每個 model 都有一個管理器 objects
def ensure_default_manager(sender, **kwargs):
    ......

    if not getattr(cls, '_default_manager', None):
        # Create the default manager, if needed.
        try:
            cls._meta.get_field('objects')
            raise ValueError("Model %s must specify a custom Manager, because it has a field named 'objects'" % cls.__name__)

        except FieldDoesNotExist:
            pass

        """
        關鍵的一步, 將一個 Manager 實例掛鉤到 cls.objects, 將 model.add_to_class() 方法羅列以下;
        def add_to_class(cls, name, value):
            if hasattr(value, 'contribute_to_class'):
                value.contribute_to_class(cls, name)
            else:
                setattr(cls, name, value)

        關鍵是 Manager 有 contribute_to_class() 方法, 由此看來, model.objects 並非一個 Manager 實例, 實際上他是一個 ManagerDescriptor 實例.
        """
        cls.add_to_class('objects', Manager())
        cls._base_manager = cls.objects

    elif not getattr(cls, '_base_manager', None):

        default_mgr = cls._default_manager.__class__

        if (default_mgr is Manager or
                getattr(default_mgr, "use_for_related_fields", False)):
            cls._base_manager = cls._default_manager

        else:
            # Default manager isn't a plain Manager class, or a suitable
            # replacement, so we walk up the base class hierarchy until we hit
            # something appropriate.
            for base_class in default_mgr.mro()[1:]:
                if (base_class is Manager or
                        getattr(base_class, "use_for_related_fields", False)):
                    cls.add_to_class('_base_manager', base_class())
                    return

由此能夠發現, Model.objects 在這個時候被添加了. 所以用戶能夠在代碼中使用 Book.objects. 至於這個函數在什麼時候被調用, 待後面會詳述 django 內部的信號機制. 暫且你能夠將其理解爲在 django 服務器啓動的時候, 這些會被自動調用就行了. 編程

Manager 實現

Manager 保護技法bash

若是能夠在 book_set = Book.objects.filter(id=1) 這一句上設置斷點, 並 step into 的時候, 發現 Book.objects 實際上的實際上不是一個 Manager 實例, 而是一個 ManagerDescriptor 實例, 這是 django 特地爲 Manager 作的一層包裝. 爲何要這麼作 ?服務器

django 規定, 只有 Model 類可使用 objects, Model 類實例不能夠. 請注意區分類和類實例之間的區別.app

我認爲這樣作是有道理的. Book.objects.filter(id=1) 返回的是 QuerySet 對象, 而 QuerySet 對象能夠當作是 Model 實例的集合, 也就是 book_set 是 Model 實例的集合. 假使 「Model 類的實例可使用 objects 屬性」, 即「從一本書中查詢書」這在語意上不經過. 只能是「從書的集合(Book)中查詢書」.編程語言

因此 django 用 ManagerDescriptor 特地爲 Manager 作的一層包裝. 能夠在 django.db.models.manager.py 中找到 ManagerDescriptor  的實現:

class ManagerDescriptor(object):
    """
    很經典的掩飾, 爲 Manager 特殊設定 Descriptor, 從而, 只能讓類訪問, 而不能讓類的實例來訪問. 具體是靠 __get__(self, instance, type=None) 方法來實現來的: 第二個參數 instance, 當 class.attr 的時候, instance 爲 None; 當 obj.attr 的時候, instance 爲 obj.
    """
    # This class ensures managers aren't accessible via model instances.
    # For example, Poll.objects works, but poll_obj.objects raises AttributeError.

    def __init__(self, manager):
        self.manager = manager

    def __get__(self, instance, type=None):
        if instance != None:
            raise AttributeError("Manager isn't accessible via %s instances" % type.__name__)
        return self.manager

所要詳述的是 __get__() 函數. python 的語法裏有修飾器(descriptor)這麼一說, 而 python 的屬性類型就是這麼實現的. descriptor 實現 __get__(), __set__(), 接着將其添加到一個類中. 譬以下面的例子:

class Celsius(object):
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        print instance,owner
        return self.value
    def __set__(self, instance, value):
        print instance,value
        self.value = float(value)

class Temperature(object):
    celsius = Celsius()

t = Temperature()
t.celsius
Temperature.celsius

當對 descriptor 賦值的時候, 其自己 __set__ 會被調用, 取值的時候 __get__() 會被調用. __set__,__get__ 函數的 instance 參數即爲類實例(因此, t.cellsius 調用 __get__() 的時候, instance 參數是 t, Temperature.celsius 調用 __get__() 的時候, instance 參數是 Temperature).

因此, 能夠經過判斷 instance 來判斷調用者是不是類實例. 也就由此能夠拒絕類實例的訪問, 發現 ManagerDescriptor 就是這麼實現的.

總結

Book.objects 其實是一個 Manager, 實際上的實際上倒是一個 ManagerDescriptor, 但真正起做用的仍是 Manager, ManagerDescriptor 是修飾器, 是 django 的保護技法.

從 Manager 的實現來看, 它的多數函數會返回 QuerySet 對象, 並且透漏了一個重點: QuerySet 對象能夠當作是 Model 實例的集合.

我已經在 github 備份了 Django 源碼的註釋: Decode-Django, 有興趣的童鞋 fork 吧.

搗亂 2013-9-24

http://daoluan.net

相關文章
相關標籤/搜索