Django在查询中打破了长查找名称

时间:2017-03-02 16:54:30

标签: python django django-models django-orm pep8

假设有一行代码使用包含很长“查找名称”的Django ORM来执行查询:

QuerySet.filter(myfk__child__onetoone__another__manytomany__relation__monster__relationship__mycustomlookup=':P')

我想要排除要跟踪pep8的行,特别是79 characters limit

我知道我们可以这样做:

   QuerySet.filter(
      **{
        'myfk__child__onetoone__another' 
        '__manytomany__relation__monster' 
        '__relationship__mycustomlookup': ':P'
      }
    )

但我想知道是否还有另一种,也许是更多的pythonic /接受的方式?

6 个答案:

答案 0 :(得分:9)

使用LOOKUP_SEP加入查找名称可能会更加苍白?

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'})

答案 1 :(得分:4)

pep8 says

  

包装长行的首选方法是在括号,括号和括号内使用Python隐含的行继续。

这就是你所做的事情,所以我认为你所获得的是最py(或者,至少是最经常)的方式。

答案 2 :(得分:3)

Django项目存储库本身在.editorconfigsetup.cfg中配置 max-line-length到119 个字符(请参见两者中突出显示的行)链接)。所有现代代码检查器(pycodestyle,pylint,pyflakes, pep8 )和编辑器都了解此配置并接受它而不是79 ch。

反思:我也更喜欢写79 ch,因为它通常更易读,但在你的情况下,一行119 chars long肯定比通过**{...}将变量名称拆分为短字符串更具可读性。如果在裸机终端中使用Python(例如Linux安装程序脚本),则非常短的行非常重要。对于Django,您通常有一个更好的本地伪终端或SSH终端。 Github在每个视图中都支持119个字符。也许用于并排解决合并冲突的图形工具可能需要在某些监视器上水平滚动。另一方面,自动合并或差异工具可能会更频繁地失败,因为相同行的重复序列仅由79 ch规则的断行创建。

答案 3 :(得分:3)

编辑 (此处有一个简单的更具吸引力的答案。原始详细答案如下所示。)

我编写了一个模块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 ariable”字段...因此我使用字母 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的方法,许多查找更具可读性。我可以看到.date()是一种查找方法,但.date是一个字段。

这不是魔术。只有带参数或关系运算符的方法才是查找的最后一部分。不带参数的方法只是一个查找符号。总是有值的东西。表达式被编译为Q表达式,包括布尔表达式。它们可以在类似的项目中轻松重用,保存到变量等,而exclude(..)条件而不是缺少!=运算符的可重用性较低。

(目前还没有不支持的功能。已经编写了一些测试。如果我得到足够的反馈,它可以是一个包。它比经典的name=value更加冗长,适用于简单的情况。< / p>

一个不同的答案,如果你喜欢可读的过滤器,可能有相关领域的长链,即使它们很复杂。

我今天写了一个简单的模块 django_dot_filter.py ,允许对相关模型上的字段使用点语法,并使用运算符==,!=,&lt;,&lt; =,&gt ;,&gt; =用于条件。它可以使用按位运算符〜| &安培;像布尔运算符类似于Q objects使用,但由于运算符的优先级,比较必须括在括号中。它受到SQLAlchemy和Pandas中使用的语法的启发。

doc string:

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'])
    >>>                             )
    """

(如果与dot一起使用,类“V”会自动创建一个新实例。所有元素在关系运算符之后或.in_(iterable)方法之后编译为Q表达式,并再次删除该实例。)

来自测试的一些示例

    #       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'))

这是一个受你的问题启发的小笑话,但它确实有效。我记得(核心开发人员?模糊记忆)的一些旧讨论,如果Django是一个新项目,语法name__operator=value将不会被再次使用。它非常简洁,但可读性较差。有两种官方语法为时已晚。

答案 4 :(得分:1)

我认为答案取决于这种事情发生的频率。 如果你倾向于在代码中的任何地方使用这样的密钥,那么就像一个类似的小包装器 答案中建议的解决方案可能会有所帮助。

如果它是一次性案件(好吧,不是一次性但很少见的情况)我会保留原样,只需用#noqa标记即可 制作短片的指标和你的代码审稿人很高兴,否则你只会严重妨碍可读性,因为你只是为了缩短你的密钥长度而做的所有这些技巧并不明显

BTW谷歌代码风格提出了79列规则中的一些排除 https://google.github.io/styleguide/pyguide.html?showone=Line_length#Line_length(注释中的长导入语句和URL)因此应明智地遵循任何规则

答案 5 :(得分:0)

晚了聚会(偶然发现了这个东西),但是您可以编写一个返回Django Q对象的函数,以从主线代码中删除丑陋的长过滤/排除定义。

def q_monster(value):
    return Q(
      **{
        'myfk__child__onetoone__another' 
        '__manytomany__relation__monster' 
        '__relationship__mycustomlookup': value
      }
    )

然后主线看起来像

from my_Q import q_monster
...
    Queryset.filter( q_monster( ':P'))

另一种选择是将丑陋的代码打包为一种方法(可能是唯一可能应用于其的模型),然后返回整个过滤后的查询集。主线代码将如下所示:

yukky_objects = MyModel.monster_filter(':P')

,细节封装在模型的下半部,只有需要知道丑陋细节的人才需要看。 (我也无法考虑不能将其放在抽象模型基类中的任何原因。)