Django F()表达式

Django F()表达式

一个F()对象代表一个模型字段的值或注释列。使用它可以直接引用模型字段的值并执行数据库操作而不用把它们导入到python的内存中。
相反,Django使用F()对象生成一个描述数据库级别所需操作的SQL表达式。
通过一个例子很容易理解。通常,有人会这样做:

reporter = Reporters.objects.get(name='Tintin')
reporter.stories_field += 1
reporter.save()

这里我们从数据库中取出reporter.stories_field的值放入内存中并使用我们熟悉的python运算符操作它,然后把对象保存到数据库中。但是我们还可以这样做:

from django.db.models import F

reporter = Reporters.objects.get(name='Tintin')
reporter.stories_field = F('stories_field') + 1
reporter.save()

虽然reporter.stories_field = F('stories_field') + 1看起来像常规的Python为实例属性赋值,但实际上它是一个描述数据库上操作的SQL结构。
当Django遇到要给F()实例,它会覆盖标准的Python运算符来创建一个封装的SQL表达式;在这个例子中,指示数据库增加由reorter.stories_field表示的数据库字段。
无论repoter.stories_field的值是或曾是什么,Python永远不需要知道--完全由数据库来处理。Python通过Django的F()类做的所有事情仅是参考某个字段创建SQL语法来描述操作。

实例对象必须重新加载后才能获取以这种方式保存的值:

reporter = Reporters.objects.get(pk=reporter.pk)
# 或者,更简洁的操作
reporter.refresh_from_db()

像上面这样在单个实例中使用,F()可以通过updage()方法被用在QuerySets对象实例中。这样可以省略上面用到的两个查询--get()save()

reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_field=F('stories_field') + 1)

我们也可以使用update()来增加多个对象的字段值,这样比从数据中取出他们,通过循环挨个增加值,然后再保存回数据库中要更加快速:

Reporters.objects.all().update(stories_field=F('stories_field') + 1)

因此,F()可以通过以下方式提供性能优势:

  • 直接在数据库中操作而不是python
  • 减少一些操作所需的数据库查询次数

使用F()避免竞争关系

使用F()的另一个好处是使用数据库来更新字段而不是用Python,以此避免竞争关系。
如果有两个Python线程执行上面第一个示例,A线程可能在B线程从数据库检索数据后来检索、增加并保存某个字段值。那么B线程的保存将基于他最初检索到的原始值;而A线程的工作将会丢失。
如果有数据库自身负责更新字段,这个过程将会更加健壮:在执行sasve()update()时基于数据库中当前的字段值,而不是基于被检索出的实例的值。

F()操作在Model.save()后会持续存在

分配给模型字段的F()对象在模型实例保存后依然保持不变,并会应用于之后的每个save()。例如:

reporter = Reporters.objects.get(name='Tintin')
reporter.stories_field = F('stories_field') + 1
reporter.save()  # F()表达式第一次执行

reporter.name = 'Tintin Jr')
reporter.save()  # F()表达式第二次执行

stories_field将会被更新两次!如果初始值为1,那么最终值将会是3

在过滤器(filter)中使用F()

F()QuerySet过滤器中也非常有用,通过它可以实现基于自身字段值来过滤一组对象,而不是通过Python值。
F()实例充当查询中一个模型字段的引用。这些引用在查询过滤器可以被用来比较同一模型实例的两个不同字段的值。
例如,我们要查找所有Blog记录中comment多于pingback的记录,我们构建一个F()对象来引用pingback的数量:

"""
class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self):
        return self.headline
"""

from django.db.models import F
Entry.objects.filter(n_commnets__gt=F('n_pingbacks'))

Django支持F()对象使用加、减、乘、除、取模和幂运算等算术操作,两个操作数可以是常数或F()对象。要查询blog记录中comment大于两本pingback的记录,修改查询为:

Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

为了查询rating 比pingback 和comment 数目总和要小的记录,我们将这样查询:

Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

你还可以在F()对象中使用双下划线标记来跨越关联关系。 带有双下划线的F()对象将引入任何需要的join 操作以访问关联的对象。 例如,如要获取author 的名字与blog 名字相同的Entry,我们可以这样查询:

Entry.objects.filter(authors__name=F('blog__name'))

对于date 和date/time 字段,你可以给它们加上或减去一个timedelta对象。 下面的例子将返回发布超过3天后被修改的所有Entry:

from datetime import timedelta
Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

在annotate中使用F()

F()可以使用算术运算组合不同字段在模型中创建动态字段。

company = Company.objects.annotate(chairs_needed=F('num_employee') - F('num_chairs'))

如果组合的是不同类型的字段,你需要告诉Django返回值是哪种字段类型。由于F()不直接支持output_field,所以需要使用ExpressionWrapper来封装表达式:

from django.db.models import DatetimeField, ExpressionWrapper, F
Ticket.objects.annotate(
    expires=ExpressionWrapper(
        F('active_at') + F('duration'), output_field=DateTimeField()
    )
)

当引用关联字段(如ForeignKey)时,F()返回的主键值而不是一个模型实例:

>>> car = Company.objects.annotate(built_by=F('manufacturrer'))[0]
>>> car.manufacturer
<Manufacturer: Toyota>
>>> car.built_by
3

使用F()对null值排序

使用F()和传递nulls_firstnulls_last参数给Expression.asc()desc()来控制字段的null值的排序。默认情况下这个排序取决于你的数据库。
例如,要将未联系过(last_contactednull)的公司排在已联系过的公司后面:

from django.db.models import F
Company.objects.order_by(F('last_contacted').desc(nulls_last=True))

翻译自Django文档

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,922评论 2 89
  • 模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。软件设计中通常用耦合度和内聚...
    riverstation阅读 2,068评论 0 8
  • 冷风寒冽,冷却了清晨和午后的时光,虽乌云漫步,树梢萧索,却还算标准的腊月时节。入了冬的校园很是破败,脚下还残留着昨...
    隼梦阅读 146评论 0 2
  • 欢迎访问我的博客查看原文:http://wangnan.tech 注:文本整理自《ELKstack权威指南》 架构...
    GhostStories阅读 19,821评论 0 31
  • 作者:阿西莫夫(Isaac Asimov) 译者:青铮 这是我所有的故事中最钟爱的一篇。 我试图把人类亿万年的历程...
    青铮的Fantasy笔记簿阅读 37,199评论 7 79