Django QuerySet浅析

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这个类

    @classmethod
    def 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类中了

    @classmethod
    def _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_method

        new_methods = {}
        # Refs http://bugs.python.org/issue1785.
        predicate = inspect.isfunction if six.PY3 else inspect.ismethod
        for 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类的关系如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QZM4c9YA-1619149785891)(./img/queryset/manager.png)]

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 = q
    obj = 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)>}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值