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

Django breaking long lookup names on queries

假设有一行代码使用 Django ORM 执行查询,其中包含很长的 'lookup name':

QuerySet.filter(myfk__child__onetoone__another__manytomany__relation__monster__relationship__mycustomlookup=':P')

我想断线关注pep8, specially 79 characters limit

我知道我们可以这样做:

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

但我想知道是否还有另一个,也许更多 pythonic/accepted,方式?

pep8 says:

The preferred way of wrapping long lines is by using Python's implied line continuation inside parentheses, brackets and braces.

这就是你所做的,所以我认为你得到的是最 pythonic(或者,至少,最 pep8ic)的方法。

也许使用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'})

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

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

EDIT (这里是一个简化的更有吸引力的答案。原始详细答案在下面的下划线。)

我写了一个模块 django_dot_filter.py,它有助于更​​自然和可读地编写查询过滤器。表达式以符号 V 开头,名称由点分隔:

from django_dot_filter import V

QuerySet.filter(V.myfk.child.onetoone.another.manytomany
                .relation.monster.relationship
                .mycustomlookup == ':P')

我读起来像 "this unknown Variable" 的字段...因此我使用字母 V。 class 实际上只是一个符号,后面可以跟点、方法、运算符等,所有内容都由 . 分隔,而不是 __

支持标准可读关系运算符,如 <<===!=,也支持括号和布尔运算符 &|, ~.

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 这样的 classic 方式编写。许多查找作为带有参数 V.my_field.regex(r'^[abc]') 而不是 my_field__regex=value 的方法更具可读性。对我来说,看到 .date() 是一种查找方法,但 .date 是一个字段的约定更具可读性。

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

(目前没有不支持的特性,已经写了一些测试,如果我得到足够的反馈,它可以打包。它比classic好name=value更冗长一点, 适用于简单的情况。



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

我今天写了一个简单的模块django_dot_filter.py,它允许对相关模型的字段使用点语法,并使用运算符==、!=、<、< =、>、>= 用于条件。它可以使用位运算符~ | & 作为布尔运算符类似于 Q objects 使用,但由于运算符优先级,比较必须括在括号中。它的灵感来自 SQLAlchemy 和 Pandas.

中使用的语法

文档字符串:

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

(class "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 将不会再次使用。它非常简洁,但可读性较差。来不及有两个官方语法。

我认为答案取决于这种事情发生的频率。 如果你倾向于在你的代码中到处使用这样的键,一个类似于 答案中建议的解决方案可能会有所帮助。

如果是一次性案例(好吧,不是一次性而是罕见案例)我会保持原样并用#noqa 标记它 让 linters 和你的代码审查者高兴的指标,否则你只会大大妨碍可读性,因为你做所有这些技巧只是为了缩短你的密钥长度并不明显

顺便说一句 google 代码风格建议从 79 列规则中排除一些 https://google.github.io/styleguide/pyguide.html?showone=Line_length#Line_length(评论中的长导入语句和 URL)因此应明智地遵循任何规则

聚会晚了(无意中发现了其他东西)但是你可以写一个函数 returning Django Q 对象来从主线代码中删除难看的长 filter/exclude 定义。

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

另一种方法是将丑陋的代码打包为可能是它可能应用到的唯一模型的方法,以及 return 整个过滤查询集。主线代码看起来像

yukky_objects = MyModel.monster_filter(':P')

并且细节被封装在模型的下方,只有那些需要了解丑陋细节的人才需要查看。 (我也想不出有什么理由不能把它放在抽象模型库中 class)。