为什么Django让Python看起来不美观?

4 投票
3 回答
2056 浏览
提问于 2025-04-17 09:19

我开始学习Python编程语言是因为它的设计理念、优秀的社区,最重要的是它的语法看起来很美。不过,最近我有点失望。在我尝试自定义Django的时候,遇到了一些我觉得语法上可以更简洁的代码。我并不是一个经验丰富的Python程序员,实际上我只用了几个月的时间来认真学习它。我很希望能听听大家的看法和见解。

以下是我遇到的一些代码示例:

为什么需要这个斜杠呢?

from django.contrib.admin.util import get_model_from_relation, \
    reverse_field_path, get_limit_choices_to_from_path

这段代码能写得更优雅一些吗?

    rel_name = other_model._meta.pk.name
    self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name)
    self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path)
    self.lookup_val = request.GET.get(self.lookup_kwarg, None)
    self.lookup_val_isnull = request.GET.get(
                                  self.lookup_kwarg_isnull, None)
    self.lookup_choices = f.get_choices(include_blank=False)

我不明白的是,为什么每个if语句后面的代码要分成几行?

def has_output(self):
    if isinstance(self.field, models.related.RelatedObject) \
       and self.field.field.null or hasattr(self.field, 'rel') \
       and self.field.null:
        extra = 1
    else:
        extra = 0
    return len(self.lookup_choices) + extra > 1

这看起来真有点乱!

def choices(self, cl):
    from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
    yield {'selected': self.lookup_val is None
                       and not self.lookup_val_isnull,
           'query_string': cl.get_query_string(
                           {},
                           [self.lookup_kwarg, self.lookup_kwarg_isnull]),
           'display': _('All')}
    for pk_val, val in self.lookup_choices:
        yield {'selected': self.lookup_val == smart_unicode(pk_val),
               'query_string': cl.get_query_string(
                               {self.lookup_kwarg: pk_val},
                               [self.lookup_kwarg_isnull]),
               'display': val}
    if isinstance(self.field, models.related.RelatedObject) \
       and self.field.field.null or hasattr(self.field, 'rel') \
       and self.field.null:
        yield {'selected': bool(self.lookup_val_isnull),
               'query_string': cl.get_query_string(
                               {self.lookup_kwarg_isnull: 'True'},
                               [self.lookup_kwarg]),
               'display': EMPTY_CHANGELIST_VALUE}

请不要误解我的意思,我并不是在贬低Django的许多贡献者,恰恰相反,我非常钦佩他们,并且心存感激。我意识到可能是我对Python本身的经验不足,或者是那些让语法看起来不太整洁的代码其实是Python编程语言的核心特性。

为了让大家明白,我这个问题是真诚的,我提这个问题是为了学习和讨论。如果你没有什么有建设性的意见,请不要回复。

谢谢你们

3 个回答

1

关于这个\,很多时候我们希望一行代码的长度不超过80个字符。这有很多原因,比如这样更容易阅读,或者可以同时打开多个文件而不需要滚动,都是一些传统的做法。

第一个例子,如果没有这个反斜杠,可能会写成

from django.contrib.admin.util import get_model_from_relation, reverse_field_path, get_limit_choices_to_from_path

你会发现要向右滚动才能看到完整的内容,这样很不方便。为了把这些导入的内容控制在80个字符以内,你可以这样写

from django.contrib.admin.util import get_model_from_relation
from django.contrib.admin.util import reverse_field_path
from django.contrib.admin.util import get_limit_choices_to_from_path

但这样你就得重复导入的模块,看起来就不太美观。最终这其实是个人风格和偏好的问题。

更一般来说,库的代码有时候看起来会比较杂乱或不太常规——有时候为了提供更好的接口,我们可能会牺牲代码的整洁性。比如Django就经常这样。声明模型的语法,或者自定义管理界面,甚至使用关键字参数来进行数据库查询,这些用起来都很方便,但让人搞清楚背后的代码却可能会有点困难。

3

我明白,可能是因为我对Python的经验不足,或者是代码中的某些部分让语法看起来不太整洁,但实际上这些都是Python编程语言的核心特性。

就是这样。当你继续编写代码时,你会意识到你所经历的那些问题其实都是因为一切对你来说都是新的。这样的代码在任何编程语言中都会出现,因为编程语言的作用就是描述对象之间的沟通,以及对消息做出反应的函数。

一个很好的练习就是快速浏览一下代码库,感受一下其中的内容。过一段时间后,你会逐渐习惯这些代码和它们之间的关系。

所以总结一下,这种“丑陋”并不是Python特有的,但当你开始熟悉这门语言时,你会慢慢把它看作只是代码,而不是觉得它难看。随着你变得越来越熟练,你会自然而然地流利使用这门语言。

2

1) 你需要使用反斜杠,因为通常情况下,Django 遵循 pep8 规范,要求每行最多 80 个字符。不过,更好的写法是:

from django.contrib.admin.util import (get_model_from_relation,
    reverse_field_path, get_limit_choices_to_from_path)

\ 一般来说应该尽量避免使用。

2) 这段代码并没有什么不优雅的地方。只是为了创建查找所需的属性。你觉得它不优雅的原因是什么呢?你希望怎么写会更好呢?

3) 这又是为了让行数少于 80 个字符。可以用 () 来重写,使其更简短:

def has_output(self):
    extra = (isinstance(self.field, models.related.RelatedObject) and 
             self.field.field.null or hasattr(self.field, 'rel') and self.field.null)
    extra = 1 if extra else 0
    return len(self.lookup_choices) + extra > 1

不过,由于 Django 使用的是 Python 2.4(我想他们很快会升级版本,或者已经升级了),所以不能使用内联的 if-else

另一方面,也可以用更简短的方式来写:

def has_output(self):
    if isinstance(self.field, models.related.RelatedObject) \
       and self.field.field.null or hasattr(self.field, 'rel') \
       and self.field.null:
        return len(self.lookup_choices) > 0
    else:
        return len(self.lookup_choices) > 1

但我觉得原来的写法稍微清晰一些,因为有了 extra 这个变量。在这里你需要加个注释,说明为什么是 0 或 1。而有了 extra 就不需要注释了,完全明了。我不喜欢注释,所以我更喜欢第一种写法 :-)

4) 这确实看起来有点乱。我觉得把它分成三个小方法会更好,每个方法可能会返回一些东西。不过我不确定在 Python 2.4(或 Python 2.5)中是否允许从子程序中返回值(我记得这个特性好像是后来才引入的,甚至是在 Python 3 中)。无论如何,我会把这些字典的创建放到单独的方法中,因为这看起来很复杂。我更喜欢这样的写法:

def choices(self, cl):
    from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
    yield self._some_default_choice()
    for pk_val, val in self.lookup_choices:
        yield self._choice_from_lookup_choices(pk_val, val)
    if isinstance(self.field, models.related.RelatedObject) \
       and self.field.field.null or hasattr(self.field, 'rel') \
       and self.field.null:
        yield self._some_conditional_choice()

当然,我会给子方法起一些更有意义的名字,但我没有看到完整的上下文,也不太清楚这些选择是什么。

最后:

你看到的这些是 Python 2 的极限。Django 是一个大型框架。有一些 特性 只是因为 Django 是一个已经开发了好几年的大项目,大家在不断学习新东西。幸运的是,Django 的开发者们正在慢慢去掉他们认为不好的东西,比如在 Django 1.4 中改变默认项目结构,弃用一些功能,并升级 Python 版本。你实际上可以通过阅读 Django 的代码和提问学到很多东西。你可能通过尝试重构一些代码学到的更多,了解为什么这并不简单,以及为什么必须保持原样 ;-) 试试看,这会很有趣 :-)

撰写回答