【问题标题】:Django breaking long lookup names on queriesDjango 在查询中打破长查找名称
【发布时间】:2017-07-22 12:39:51
【问题描述】:

假设有一行代码使用包含很长“查找名称”的 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/被接受的方式?

【问题讨论】:

  • 比较发布的答案,更喜欢您自己的解决方案。如果有杂乱的业务需求,您将无法美化代码。然后再次阅读代码时使用成本最低的格式。

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


【解决方案1】:

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

【讨论】:

  • 如果我真的需要经常打破这个,那么我将实现该功能以支持带有点符号和普通比较运算符的自然语法“==”、“models.Q 条件。我的新课程可能是例如Z。例如Queryset.filter(Z.myfk.child.onetoone.another.manytomany.relation.monster.relationship.mycustomlookup <= ':P', another_filter_condition)。这很容易破碎。有人对此感兴趣吗? (或者你想在 Django 中看到它吗?)
【解决方案2】:

pep8 says:

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

这就是你所做的,所以我认为你所拥有的是最 Pythonic(或者,至少是最 pep8ic)的方式。

【讨论】:

    【解决方案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')
    

    我把它读成带有字段的“这个未知的Variable”......因此我使用了字母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稍微冗长一点,适合简单的情况。



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

    我今天写了一个简单的模块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'])
        >>>                             )
        """
    

    (类“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】:

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

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

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

          聚会迟到了(偶然发现了这个寻找其他东西),但您可以编写一个返回 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')
          

          细节被封装在模型的最底层,只有需要了解丑陋细节的人才需要查看。 (我也想不出有什么理由不能把它放在抽象模型基类中)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-01-12
            • 2012-03-04
            • 2012-06-09
            • 2010-11-16
            • 1970-01-01
            • 2016-02-13
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多