让我们假设有一行代码使用包含很长“查找名称”的Django ORM来执行查询:
QuerySet.filter(myfk__child__onetoone__another__manytomany__relation__monster__relationship__mycustomlookup=':P')
我想打破线路跟随qazxsw poi,尤其是qazxsw poi
我知道我们可以这样做:
pep8
但我想知道是否还有另一种,也许更多的pythonic /接受的方式?
也许使用79 characters limit加入查找名称有点可口?
QuerySet.filter(
**{
'myfk__child__onetoone__another'
'__manytomany__relation__monster'
'__relationship__mycustomlookup': ':P'
}
)
LOOKUP_SEP
:
包装长行的首选方法是在括号,括号和括号内使用Python隐含的行继续。
这就是你所做的,所以我认为你所拥有的是最pythonic(或者,至少是最pep8ic)的方式。
Django项目存储库本身将from django.db.models.constants import LOOKUP_SEP
lookup = LOOKUP_SEP.join(['myfk', 'child', 'onetoone', 'another', 'manytomany',
'relation', 'monster', 'relationship',
'mycustomlookup'])
QuerySet.filter(**{lookup:':P'})
配置为pep8 says和max-line-length
中的119个字符(参见两个链接中突出显示的行)。所有现代代码检查器(pycodestyle,pylint,pyflakes,pep8)和编辑都理解这种配置并接受它而不是79 ch。
反思:我也更喜欢写79 ch,因为它通常更易读,但在你的情况下,一行119 chars long肯定比.editorconfig将变量名称拆分为短字符串更具可读性。如果在裸机终端中使用Python(例如Linux安装程序脚本),则非常短的行非常重要。对于Django,您通常有一个更好的本地伪终端或SSH终端。 Github在每个视图中都支持119个字符。也许用于并排解决合并冲突的图形工具可能需要在某些监视器上水平滚动。另一方面,自动合并或差异工具会更频繁地失败,因为相同行的重复序列仅由79 ch规则的断行创建。
编辑(一个简化的更有吸引力的答案在这里。原始的详细答案在下面。)
我写了一个模块setup.cfg,它有助于更自然和可读地编写查询过滤器。表达式以符号**{...}
开头,名称以点分隔:
django_dot_filter.py
我把它读作“带有字段的未知变量”...因此我使用字母V
。那个类实际上只是一个符号,可以跟随点,方法,运算符等,一切都由from django_dot_filter import V
QuerySet.filter(V.myfk.child.onetoone.another.manytomany
.relation.monster.relationship
.mycustomlookup == ':P')
而不是V
分隔。
支持标准可读关系运算符,如.
,__
,<
或<=
,括号和布尔运算符==
,!=
,&
。
|
每个查找都可以像经典方式~
或像Queryset.filter((V.some_related.my_field >= 10)
| ~V.field_x.startswith('Y') & (V.date_field.year() == 2017)
& V.price.range(10, 100))
方法一样编写。许多查找作为一个带有参数V.date_field.year == 2017
而不是V.date_field.year() == 2017
的方法更具可读性。对于我来说,看到V.my_field.regex(r'^[abc]')
是一种查找方法的约定更具可读性,但my_field__regex=value
是一个字段。
这不是魔术。只有带参数或关系运算符的方法才是查找的最后一部分。不带参数的方法只是一个查找符号。总是有值的东西。表达式被编译为Q表达式,包括布尔表达式。它们可以很容易地在类似的项目中重用,保存到变量等,而.date()
条件而不是缺少.date
运算符是不太可重用的。
(目前还没有不支持的功能。已经编写了一些测试。如果我得到足够的反馈,它可以打包。它比经典的好exclude(..)
更加冗长,适合简单的情况。
一个不同的答案,如果你喜欢可读的过滤器,可能有相关领域的长链,即使它们很复杂。
我今天写了一个简单的模块!=
,它允许对相关模型上的字段使用点语法,并使用运算符==,!=,<,<=,>,> =作为条件。它可以使用按位运算符〜| &作为布尔运算符,类似于name=value
使用,但由于运算符优先级,比较必须括在括号中。它受到SQLAlchemy和Pandas中使用的语法的启发。
doc字符串:
django_dot_filter.py
(如果与点一起使用,类“V”会自动创建一个新实例。所有元素在关系运算符之后或在Q objects方法之后编译为Q表达式,并再次删除该实例。)
测试的一些例子
class V(...):
"""
Syntax suger for more readable queryset filters with "." instead "__"
The name "V" can be understand like "variable", because a shortcut for
"field" is occupied yet.
The syntax is very similar to SQLAlchemy or Pandas.
Operators < <= == != >= > are supperted in filters.
>>> from django_dot_filter import V
>>>
>>> qs = Product.objects.filter(V.category.name == 'books',
>>> V.name >= 'B', V.name < 'F',
>>> (V.price < 15) | (V.date_created != today),
>>> ~V.option.in_(['ABC', 'XYZ'])
>>> )
This is the same as
>>> qs = Product.objects.filter(category__name='books',
>>> name__gte='B', name__lt='F',
>>> Q(price__lt=15) | ~Q(date_created=today),
>>> ~Q(option__in=['ABC', 'XYZ'])
>>> )
"""
这是一个受你的问题启发的小笑话,但它确实有效。我记得(核心开发人员?模糊记忆)的一些旧讨论,如果Django是一个新项目,语法.in_(iterable)
将不会再次被使用。它非常简洁,但可读性较差。现在有两种官方语法为时已晚。
我认为答案取决于这种事情发生的频率。如果您倾向于在代码中的任何地方使用此类密钥,那么类似于答案中建议的解决方案的小包装可能会有所帮助。
如果它是一次性的情况(好吧,不是一次性但很少见的情况)我会保留原样,只需用#noqa指示标记就可以制作短线,而你的代码审查员也很开心,否则你只会大大妨碍可读性因为为了缩短你的密钥长度,你所做的所有这些技巧并不明显
BTW谷歌代码风格提出了79列规则 # this is V. syntax compiled Q syntax
test_eq(V.a.b.c == 1, Q(a__b__c=1))
test_eq(V.a == 1, Q(a=1))
test_eq(V.a != 1, ~Q(a=1))
test_eq(V.a < 2, Q(a__lt=2))
test_eq(V.a <= 3, Q(a__lte=3))
test_eq(V.a > 'abc', Q(a__gt='abc'))
test_eq(V.a >= 3.14, Q(a__gte=3.14))
test_eq((V.a == 1) & (V.b == 2), Q(a=1) & Q(b=2))
test_eq((V.a == 1) | (V.b == 2), Q(a=1) | Q(b=2))
test_eq((V.a == 1) | ~(V.b == 2), Q(a=1) | ~Q(b=2))
# method "in_(..)" is used because the word "in" is reserved.
test_eq(V.first_name.in_([1, 2]), Q(first_name__in=[1, 2]))
test_eq(~V.a.in_(('Tim', 'Joe')), ~Q(a__in=('Tim', 'Joe')))
# this should be eventually improved to support all lookup
# functions automatically e.g. by ".contains('abc')" instead of "=="
test_eq(V.a.contains == 'abc', Q(a__contains='abc'))
(评论中的长导入语句和URL)中的一些排除,因此应明智地遵循任何规则
晚到了派对(偶然发现了这个寻找其他东西)但是你可以写一个函数返回一个Django Q对象来从主线代码中删除丑陋的长过滤器/排除定义。
name__operator=value
然后主线看起来像
https://google.github.io/styleguide/pyguide.html?showone=Line_length#Line_length
另一种方法是将丑陋的代码打包为可能是唯一可能应用的模型的方法,并返回整个过滤的查询集。然后主线代码看起来像
def q_monster(value):
return Q(
**{
'myfk__child__onetoone__another'
'__manytomany__relation__monster'
'__relationship__mycustomlookup': value
}
)
并且细节被封装在模型的下方,只有那些需要知道丑陋细节的人才需要看。 (我想不出你有什么理由不把它放在抽象模型基类中)。