Django ORM 查询管理器源码解析


Posted in Python onAugust 05, 2019

ORM 查询管理器

对于 ORM 定义: 对象关系映射, Object Relational Mapping, ORM, 是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。ORM 能大大简化并抽象数据库的操作.

假设 django 的一个工程中包含一个名为 Book 的模块(model), 在 views.py 的函数中可能会写出查询语句:

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

ORM 的作用就是这样, 并不需要写更复杂的 SQL 语句, 所有的的事情都被 ORM 代劳了.

上面中, Book 实际上是一个 Model 实例, 但先是从 Book.objects 开始说起. Book.objects 实际上是一个 Manager 类实例, 每个 Model 都会有一个, 用户的查询操作几乎是从这里开始的. 万万可以将 Model 实例理解为关系表中的一个表项数据, 而 Manager 实例可以理解数据库查询的入口.

实际上, 无论如何都在 Model 类的源码中找到任何 objects 属性的字眼, 因此它肯定是在某个时间点上增加的. 可以在 django.db.models.manager.py 中找到下面的函数:

这个函数确保每一个 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 保护技法

如果可以在 book_set = Book.objects.filter(id=1) 这一句上设置断点, 并 step into 的时候, 发现 Book.objects 实际上的实际上不是一个 Manager 实例, 而是一个 ManagerDescriptor 实例, 这是 django 特意为 Manager 做的一层包装. 为什么要这么做 ?

django 规定, 只有 Model 类可以使用 objects, Model 类实例不可以. 请注意区分类和类实例之间的区别.

我认为这样做是有道理的. 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 吧.

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python使用shelve模块实现简单数据存储的方法
May 20 Python
如何优雅地改进Django中的模板碎片缓存详解
Jul 04 Python
使用Python抓取豆瓣影评数据的方法
Oct 17 Python
python根据txt文本批量创建文件夹
Dec 08 Python
使用GitHub和Python实现持续部署的方法
May 09 Python
python3 自动识别usb连接状态,即对usb重连的判断方法
Jul 03 Python
详解从Django Allauth中进行登录改造小结
Dec 18 Python
Python阶乘求和的代码详解
Feb 14 Python
使用Tkinter制作信息提示框
Feb 18 Python
Python实现的北京积分落户数据分析示例
Mar 27 Python
Pytest框架之fixture的详细使用教程
Apr 07 Python
python实现腾讯滑块验证码识别
Apr 27 Python
python实现车牌识别的示例代码
Aug 05 #Python
使用python实现滑动验证码功能
Aug 05 #Python
Django 源码WSGI剖析过程详解
Aug 05 #Python
Python使用itchat 功能分析微信好友性别和位置
Aug 05 #Python
Python队列RabbitMQ 使用方法实例记录
Aug 05 #Python
Python 通过微信控制实现app定位发送到个人服务器再转发微信服务器接收位置信息
Aug 05 #Python
基于python框架Scrapy爬取自己的博客内容过程详解
Aug 05 #Python
You might like
延长phpmyadmin登录时间的方法
2011/02/06 PHP
PHP 之Section与Cookie使用总结
2012/09/14 PHP
一个简洁实用的PHP缓存类完整实例
2014/07/26 PHP
为PHP安装imagick时出现Cannot locate header file MagickWand.h错误的解决方法
2014/11/03 PHP
php结合curl实现多线程抓取
2015/07/09 PHP
PHP实现的二分查找算法实例分析
2017/12/19 PHP
jquery tab插件制作实现代码
2010/06/22 Javascript
从数据结构的角度分析 for each in 比 for in 快的多
2013/07/07 Javascript
火狐下table中创建form导致两个table之间出现空白
2013/09/02 Javascript
javascript判断是否按回车键并解决浏览器之间的差异
2014/05/13 Javascript
JavaScript fontsize方法入门实例(按照指定的尺寸来显示字符串)
2014/10/17 Javascript
JavaScript中使用Object.create()创建对象介绍
2014/12/30 Javascript
javascript实现可拖动变色并关闭层窗口实例
2015/05/15 Javascript
js实现动态加载脚本的方法实例汇总
2015/11/02 Javascript
Jquery和angularjs获取check框选中的值的方法汇总
2016/01/17 Javascript
Javascript Function.prototype.bind详细分析
2016/12/29 Javascript
详解webpack2+React 实例demo
2017/09/11 Javascript
Vue组件开发之LeanCloud带图形校验码的短信发送功能
2017/11/07 Javascript
vue获取当前激活路由的方法
2018/03/17 Javascript
vue+iview 实现可编辑表格的示例代码
2018/10/31 Javascript
NodeJs 实现简单WebSocket即时通讯的示例代码
2019/08/05 NodeJs
VUE实现强制渲染,强制更新
2019/10/29 Javascript
解决vue项目刷新后,导航菜单高亮显示的位置不对问题
2019/11/01 Javascript
[02:17]TI4西雅图DOTA2前线报道 啸天mik夫妻档解说
2014/07/08 DOTA
[01:14]DOTA2 7.22版本新增神杖效果展示(智力英雄篇)
2019/05/29 DOTA
Python 异常处理的实例详解
2017/09/11 Python
python实现图片处理和特征提取详解
2017/11/13 Python
关于Python中的向量相加和numpy中的向量相加效率对比
2019/08/26 Python
详解Python 字符串相似性的几种度量方法
2019/08/29 Python
Xadmin+rules实现多选行权限方式(级联效果)
2020/04/07 Python
越南电子产品购物网站:FPT Shop
2017/12/02 全球购物
公司授权委托书格式样本
2014/10/01 职场文书
大学生万能检讨书范例
2014/10/04 职场文书
美甲店的创业计划书模板
2019/08/23 职场文书
解读MySQL的客户端和服务端协议
2021/05/10 MySQL
25张裸眼3D图片,带你重温童年的记忆,感受3D的魅力
2022/02/06 杂记