QuerySet 源码浅析

模型.objects:

from django.http import HttpResponse
from .models import Book
def index(request):print(type(Book.objects))return HttpResponse("index")

控制台打印信息

<class 'django.db.models.Manager'>

也就是说对象是django.db.models.manager.Manager的对象,Ctrl + B进入路径可以看到

class Manager(BaseManager.from_queryset(QuerySet)):pass

这个类是一个空壳类,它上面的所有方法都是从BaseManager这个类上from_queryset方法上拷贝过来的。看懂了from_queryset的返回,也就看懂了QuerySetobjects也就知道该如何使用。

from_queryset调用了QuerySet这个类

    @classmethoddef from_queryset(cls, queryset_class, class_name=None):if class_name is None:class_name = '%sFrom%s' % (cls.__name__, queryset_class.__name__)class_dict = {'_queryset_class': queryset_class,}class_dict.update(cls._get_queryset_methods(queryset_class))return type(class_name, (cls,), class_dict)

可以看到,class_name默认为空,cls.__name__ = BaseManagerqueryset_class.__name__QuerySet,则拼接后的class_name= “BaseManagerFromQuerySet”。

这里,我们只要再了解下_get_queryset_methods这个方法就可以知道Query Set是怎么“跑到”Manager类中了

    @classmethoddef _get_queryset_methods(cls, queryset_class):def create_method(name, method):def manager_method(self, *args, **kwargs):return getattr(self.get_queryset(), name)(*args, **kwargs)manager_method.__name__ = method.__name__manager_method.__doc__ = method.__doc__return manager_methodnew_methods = {}# Refs http://bugs.python.org/issue1785.predicate = inspect.isfunction if six.PY3 else inspect.ismethodfor name, method in inspect.getmembers(queryset_class, predicate=predicate):# Only copy missing methods.if hasattr(cls, name):continue# Only copy public methods or methods with the attribute `queryset_only=False`.queryset_only = getattr(method, 'queryset_only', None)if queryset_only or (queryset_only is None and name.startswith('_')):continue# Copy the method onto the manager.new_methods[name] = create_method(name, method)return new_methods

其中函数create_method创建方法,queryset_class_get_queryset_methods传入的参数,也就是QuerySet,getmembers会从QuerySet获取其所有的方法,进行过滤后,然后把遍历出来的方法存放到字典new_methods中,然后把new_methods返回,来更新到class_dict中,回到from_queryset函数,我们可以得到以下结论

# type动态的时候创建类(class是静态地创建一个类)
# 第一个参数是用来指定创建的类的名字。创建的类名是:BaseManagerFromQuerySet
# 第二个参数是用来指定这个类的父类。
# 第三个参数是个字典,其中第一个键是用来指定这个类的一些属性,另一个键是用来指定这些类的方法return type(class_name, (cls,), class_dict)

Manager类与QuerySet类的关系如下图:

QuerySet特性

  1. 可以切片使用,不支持负的索引:
book_list=models.Book.objects.all()
print(book_list)   #<QuerySet [<Book: python>, <Book: go>]>
book_list[0:1]   #<QuerySet [<Book: python>]>
  1. 可迭代:
book_list=models.Book.objects.all()for obj in book_list: print(obj.title,obj.price)
  1. 惰性查询:

创建查询集不会带来任何数据库的访问,只有在迭代、切片、调用len函数、list函数和进行判断时,才会真正运行这个查询。

from django.db import connection
queryset=Book.objects.all() #此时只是创建了查询集query,并没有运行,因此并没有执行相应的sql语句,要真正从数据库获得数据,需要遍历queryset:
print(connection.queries)   #打印空列表,说明并没有转化为SQL语句去执行
for article in queryset:print(article.title)    # 对queryset进行了查询,sql语句执行
print(connection.queries)   #打印SQL语句
  1. 缓存机制:

当遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。这些model会保存在queryset内置的cache中,如果再次遍历这个queryset,将直接使用缓存中的结果

执行下列代码,queryset执行两次,但sql只执行了一次;

queryResult=models.Book.objects.all()
print([a.title for a in queryResult])
print([a.create_time for a in queryResult])

重复获取查询集对象中一个特定的索引需要每次都查询数据库;

queryset = Entry.objects.all()
print queryset[5] # 访问数据库
print queryset[5] # 再次访问数据库

QuerySet方法介绍

values:用来指定在提取数据时,需要提取哪些字段。默认情况下会把表中所有的字段全部都提取出来,可以使用values来进行指定,并且使用了values方法后,提取出的QuerySet中的数据类型不是模型,而是在values方法中指定的字段和值形成的字典。

books = Book.objects.values('id', 'name')
print(books)
# 列表中包含字典
# <QuerySet [{'id': 1, 'name': '三国演义'}, {'id': 2, 'name': '水浒传'}]>

values_list:类似于values。只不过返回的QuerySet中,存储的不是字典,而是元组。

books = Book.objects.values_list('id', 'name')
print(books)   # <QuerySet [(1, '三国演义'), (2, '水浒传'), (3, '西游记'), (4, '红楼梦')]>

select_related:在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。

select_related() 接受可变长参数,每个参数是需要获取的外键(父表的内容)的字段名,以及外键的外键的字段名、外键的外键的外键…。若要选择外键的外键需要使用两个下划线“__”来连接。

person = Person.objects.select_related('living__province')

select_related() 也可以不加参数,这样表示要求Django尽可能深的select_related。

person = Person.objects.select_related()

select_related() 接受depth参数,depth参数可以确定select_related的深度。Django会递归遍历指定深度内的所有的OneToOneField和ForeignKey。

person = Person.objects.select_related(depth = d)
#d=1 相当于 select_related(‘hometown’,’living’)
#d=2 相当于 select_related(‘hometown__province’,’living__province’)

可以通过传入一个None来清空之前的select_related。

prefetch_related():这个方法和select_related非常的类似,就是在访问多个表中的数据的时候,减少查询的次数。这个方法是为了解决多对一和多对多的关系的查询问题。

Province.objects.prefetch_related('city_set')

和select_related()一样,prefetch_related()也支持深度查询。

可以通过传入一个None来清空之前的prefetch_related。

get_or_create:根据某个条件进行查找,如果找到了那么就返回这条数据,如果没有查找到,那么就创建一个。这个方法的返回值是一个元组,元组的第一个参数obj是这个对象,第二个参数created是boolen类型,代表是否创建的。

obj, created = Author.objects.get_or_create(name="xxx", age=11)

但是,如果有的必填字段未给出,当数据库中有这条数据时,可以正常返回,如果没有这条数据,在创建数据时就会报错。

update_or_create:根据某个条件进行更新,如果找到了那么就返回更新后的数据,如果没有查找到,那么就创建一个。这个方法的返回值是一个元组,元组的第一个参数obj是这个对象,第二个参数created是boolen类型,代表是否创建的。

obj, created = Author.objects.update_or_create(name="xxx", age=11)

exists:判断某个条件的数据是否存在。如果要判断某个条件的元素是否存在,那么建议使用exists,这比使用count或者直接判断QuerySet更有效得多。因为它是先从缓存中找,没有缓存再去查找数据库。

Author.objects.filter(name__contains='xxx').exists()

aggregate:在执行聚合函数的时候,是对QuerySet整个对象的某个属性汇总,在汇总时不会使用该模型的主键进行group by进行分组,得到的是一个结果字典。同时,该方法支持聚合关联表(如使用ForeignKey)中的字段,在聚合连表中字段时,传递该字段的方式与查询连表时传递字段的方式相同,会使用到"__"。

result = Author.objects.aggregate(avg_age=Avg('age'))
print(result) # {'avg_age': 25}

annotate:这个方法不但可以执行聚合函数,也可以传递F、Q对象为当前QuerySet生成一个新的属性。这个方法一般聚合的是连表中的字段,会为当前QuerySet中的每个对象生成一个独立的摘要,为查询的模型增加一个新的属性,这个属性的值就是使用聚合函数所得到的值,在使用这个聚合函数的时候annotate会使用这个模型的主键进行group by进行分组(注意这里只有在使用聚合函数生成新字段的时候会进行group by,在使用F、Q表达式增添新字段时,并不会使用group by),然后在连表中根据分组的结果进行聚合。

使用这个方法执行聚合函数,得到的结果是一个QuerySet对象,结果依然能够调用filter()、order_by()甚至annotate()进行再次聚合。

books = Book.objects.annotate(avg=Avg('bookorder__sailprice'))

iterator:当queryset非常巨大时,cache会成为问题。处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法来获取数据,处理完数据就将其丢弃。

objs = Book.objects.all().iterator()
# iterator()可以一次只从数据库获取少量数据,这样可以节省内存
for obj in objs:print(obj.title)
#BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
for obj in objs:print(obj.title)

当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询。

raw:接收一个原始的SQL查询,执行它并返回一个django.db.models.query.RawQuerySet实例。
这个RawQuerySet实例可以迭代。

for p in Person.objects.raw('SELECT * FROM myapp_person'):print(p)

bulk_create:此方法将提供的实例列表批量插入数据库,可以一次创建多条数据。由于create()每保存一条就执行一次SQL,而bulk_create是执行一条SQL存入多条数据,这样做会快很多!方法返回插入数据的实例列表(obj_list跟objs是一样的)。

q_list = [('a', datetime.datetime.now()), ('b', datetime.datetime.now()), ('c', datetime.datetime.now())]
obj_list = []
for q in q_list:question_text, pub_date = qobj = Question(question_text=question_text, pub_date=pub_date)obj_list.append(obj)
objs = Question.objects.bulk_create(obj_list)

in_bulk:接收一个包含主键值的列表,并返回将每个主键值映射到具有给定ID的对象实例的字典。如果给定ID不存在,也不会报错,只返回数据表中有的ID。如果未提供列表,则返回查询集中的所有对象。

id_list = [1, 2, 3, 11]    #数据表没有id=11的数据
in_bulk = Question.objects.in_bulk(id_list)
print(in_bulk)
#{1: <Question: Question object (1)>, 2: <Question: Question object (2)>, 3: <Question: Question object (3)>}

Django QuerySet浅析相关推荐

  1. Django QuerySet 就学那么一点点,一点点就够了

    橡皮擦,一个逗趣的互联网高级网虫.新的系列,让我们一起进入 Django 世界. 已经完成的文章 滚雪球学 Python 第三轮,Python Web 之 Django 的世界 小手哆嗦一下,就能用 ...

  2. Django QuerySet API 文档阅读(3):QuerySet定义(一)

    原文地址:​​​​​​QuerySet API reference | Django documentation | Django QuerySet有两个属性,ordered和db: ordered: ...

  3. Django学习笔记之Django QuerySet的方法

    一般情况下,我们在写Django项目需要操作QuerySet时一些常用的方法已经满足我们日常大多数需求,比如get.filter.exclude.delete神马的感觉就已经无所不能了,但随着项目但业 ...

  4. Django QuerySet API文档

    在查询时发生了什么(When QuerySets are evaluated) QuerySet 可以被构造,过滤,切片,做为参数传递,这些行为都不会对数据库进行操作.只要你查询的时候才真正的操作数据 ...

  5. 合并多个python list以及合并多个 django QuerySet 的方法

    尊重原文作者,该文转载于: http://www.yihaomen.com/article/python/533.htm 在用python或者django写一些小工具应用的时候,有可能会遇到合并多个l ...

  6. django QuerySet

    Django对数据库的封装1--QuerySet 发布时间:2018-02-28 来源:网络 上传者:用户 关键字: manager 数据库 记录 影响 发表文章 摘要:   Django对数据库的操 ...

  7. Django QuerySet优化

    参考: https://docs.djangoproject.com/zh-hans/3.2/topics/db/optimization https://www.jianshu.com/p/aded ...

  8. Django Queryset用法

    文章目录 filter/get get/update_or_create update django查询之Q对象.F对象.聚合查询.分组查询 QuerySet 并不是执行Objects.all(),或 ...

  9. Django QuerySet速查手册

    Django为我们提供了一套与数据库交互的机制,让我们能够更便捷的对数据库进行增删改查.Django 对数据库的操作依赖于它的 ORM 系统,Django ORM 主要包括Manager.QueryS ...

最新文章

  1. java表单提交包含文件_如何同时提交表单中的文件和文本
  2. mysql踢掉登录用户_centos 强制踢掉某登录用户的方法
  3. SAP ABAP OData uri type为metadata的请求处理逻辑
  4. HttpServletRequest简述
  5. Why Not Specialize Function Templates?
  6. 华为调研了82位离职博士,任正非发电邮深讨人才流失根源
  7. PAT (Basic Level) Practice (中文)答案合集
  8. 合数阶群与素数阶群的双线性映射
  9. vue+腾讯位置服务 实现坐标拾取器功能
  10. VBA字典做数据有效性
  11. (一)移动端前端开发-移动端基础
  12. 以太坊链上的二层(layer2)扩容方案Matic(Polygon)
  13. exchange2016邮件服务器配置,Exchange Server 2016客户端访问配置
  14. Cache与主存的地址映像
  15. DeepLab语义分割
  16. 2021年施工员-土建方向-岗位技能(施工员)考试技巧及施工员-土建方向-岗位技能(施工员)实操考试视频
  17. Linux数据库管理——day10——分库分表、数据库硬件优化
  18. 论计算机取证工具软件及其检测(转)
  19. Oracle数据库sql语句练习【emp和dept的连表查询由浅入深】
  20. rust提示游戏安全违规_在Windows 10中检测到游戏安然违规

热门文章

  1. tkinter库绘图实例13:手绘画板效果
  2. 计算机网络ping所有用法,ping命令详解和使用方法【详解】
  3. Kotlin中的集合函数
  4. CF817C Really Big Numbers
  5. Django - Template标签操作
  6. Win7 硬盘分区建议
  7. 操作系统(Operating System,OS)
  8. SSM学习1|整合SSM框架
  9. python事件驱动框架_python事件驱动
  10. C# 多线程Thread.IsBackground=True的作用