---
title: "编码风格"
version: 5.2
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/5.2/internals/contributing/writing-code/coding-style/
canonical: https://djangodocs.dev/zh-hans/5.2/internals/contributing/writing-code/coding-style/
---
# 编码风格

当编写Django代码时，请遵从这些编码标准。

## 预提交检查

[pre-commit](https://pre-commit.com) 是一个用于管理预提交钩子的框架。这些钩子有助于在提交代码进行审查之前识别简单的问题。通过在代码审查之前检查这些问题，它可以让审查人员集中精力在更改本身上，还可以帮助减少 CI 运行的次数。

要使用这个工具，首先安装 `pre-commit`，然后安装 Git 钩子：

```console
$ python -m pip install pre-commit
$ pre-commit install
```

*Windows*

```doscon
...\> py -m pip install pre-commit
...\> pre-commit install
```

在第一次提交时，`pre-commit` 将安装钩子，这些钩子会安装在它们自己的环境中，并且在第一次运行时需要一点时间来安装。后续的检查将会快得多。如果发现错误，将显示相应的错误消息。如果错误与 `black` 或 `isort` 有关，那么工具会为你修复它们。请查看更改并在满意后重新暂存以进行提交。

## Python 编码风格

- 所有文件都应使用 [black](https://pypi.org/project/black/) 自动格式化工具进行格式化。如果配置了 `pre-commit`，这将由其运行。
- 项目仓库包括一个 `.editorconfig` 文件。我们建议使用支持 [EditorConfig](https://editorconfig.org/) 的文本编辑器，以避免缩进和空白字符问题。Python 文件使用 4 个空格缩进，而 HTML 文件使用2个空格。
- 除非另有约定的情况下，否则遵从 PEP8 编码规范。

  Use [flake8](https://pypi.org/project/flake8/) to check for problems in this area. Note that our
  `.flake8` file excludes some errors that we don't consider as gross
  violations. Remember that [**PEP 8**](https://peps.python.org/pep-0008/) is only a guide, so respect the style of
  the surrounding code as a primary goal.

  对于 [**PEP 8**](https://peps.python.org/pep-0008/) 的一个例外是我们对行长度的规定。如果限制代码行的长度为 79 个字符会使代码看起来显著难看或难以阅读，那么不要这样做。我们允许最多 88 个字符，因为这是 `black` 使用的行长度。当你运行 `flake8` 时，这个检查会包含在内。然而，文档、注释和文档字符串应该在 79 个字符处换行，即使 [**PEP 8**](https://peps.python.org/pep-0008/) 建议是 72 个字符。
- 字符串变量插值可以使用 [%-formatting](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)、[f-strings](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) 或 [`str.format()`](https://docs.python.org/3/library/stdtypes.html#str.format)，以提高代码的可读性为目标。

  关于可读性的最终判断留给合并者自行决定。作为指南，f-strings 应仅使用普通的变量和属性访问，在更复杂的情况下，应先进行本地变量赋值。

  ```
  # Allowed
  f"hello {user}"
  f"hello {user.name}"
  f"hello {self.user.name}"

  # Disallowed
  f"hello {get_user()}"
  f"you are {user.age * 365.25} days old"

  # Allowed with local variable assignment
  user = get_user()
  f"hello {user}"
  user_days_old = user.age * 365.25
  f"you are {user_days_old} days old"
  ```

  不应该在可能需要翻译的任何字符串，包括错误和日志消息中使用 f-strings。一般来说，`format()` 更加冗长，因此更倾向于使用其他格式化方法。

  不要浪费时间对现有代码进行无关的重构来调整格式化方法。
- 在注释中避免使用 " we "，例如使用 " Loop over " 而不是 " We loop over "。
- 在给变量，函数和方法命名时候使用下划线而不是小驼峰 (例如：  poll.get\_unique\_voters(), not poll.getUniqueVoters() )。
- 类名使用“大驼峰命名法”（或者是用在能返回类的工厂函数上面）。
- 对于文档字符串，遵从现有文档字符串风格和 PEP257 规范。
- 在测试中，使用 [`assertRaisesMessage()`](/zh-hans/5.2/topics/testing/tools/#django.test.SimpleTestCase.assertRaisesMessage) 和 [`assertWarnsMessage()`](/zh-hans/5.2/topics/testing/tools/#django.test.SimpleTestCase.assertWarnsMessage)，而不是 [`assertRaises()`](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises) 和 [`assertWarns()`](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertWarns)，这样可以检查异常或警告消息。只有在需要正则表达式匹配时才使用 [`assertRaisesRegex()`](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaisesRegex) 和 [`assertWarnsRegex()`](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertWarnsRegex)。

  在测试布尔值时，使用 [`assertIs(…, True/False)`](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIs)，而不是 [`assertTrue()`](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertTrue) 和 [`assertFalse()`](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertFalse)，这样可以检查实际的布尔值，而不是表达式的真值。
- 在测试文档字符串中，说明每个测试展示的预期行为。不要包含像 " Tests that " 或 " Ensures that " 这样的前言。

  保留工单引用用于描述复杂问题，其中工单包含无法轻易在文档字符串或注释中描述的额外细节。在句子末尾包含工单号，如下所示：

  ```
  def test_foo():
      """
      A test docstring looks like this (#123456).
      """
      ...
  ```
- Where applicable, use unpacking generalizations compliant with [**PEP 448**](https://peps.python.org/pep-0448/),
  such as merging mappings (`{**x, **y}`) or sequences (`[*a, *b]`). This
  improves performance, readability, and maintainability while reducing errors.

## 导入

- 使用 [isort](https://pypi.org/project/isort/) 来自动按照以下指南进行导入排序。

  快速入门：

  ```console
  $ python -m pip install "isort >= 7.0.0"
  $ isort .
  ```

  *Windows*

  ```doscon
  ...\> py -m pip install "isort >= 7.0.0"
  ...\> isort .
  ```

  这将从你的当前目录递归运行 `isort` ，修改所有不符合指引的文件，如果你不需要排序（例如：避免循环导入）请在你的导入里面像这样加上注释：

  ```
  import module  # isort:skip
  ```
- 把导入这样进行分组：future库，python标准库，第三方库， 其他的Django组件，本地Django组件，try/excepts块。按照字母顺序对每个分组进行排序，按照完整的模块名称。在每个分组里面把import module句子放在 from module importobjects句子前面。对于其他Django组件使用绝对导入，对本地Django组件使用相对导入。
- 在每一行上，按字母顺序排列项目，将大写字母的项目放在小写字母的项目之前分组。
- 使用括号和4个连续空格构成的缩进去打破长行，在最后一个导入之后写一个逗号，把右括号放在单独一行。

  在最后一个导入和任何代码块之间留出一个空行，在第一个函数或类前面留出两个空行。

  例如（注释仅作为解释用途）：

  *`django/contrib/admin/example.py`*

  ```python
  # future
  from __future__ import unicode_literals

  # standard library
  import json
  from itertools import chain

  # third-party
  import bcrypt

  # Django
  from django.http import Http404
  from django.http.response import (
      Http404,
      HttpResponse,
      HttpResponseNotAllowed,
      StreamingHttpResponse,
      cookie,
  )

  # local Django
  from .models import LogEntry

  # try/except
  try:
      import yaml
  except ImportError:
      yaml = None

  CONSTANT = "foo"

  class Example: ...
  ```
- 在可用时，请使用方便的导入方式。例如，这样做：

  ```
  from django.views import View
  ```

  替换成：

  ```
  from django.views.generic.base import View
  ```

## 模版风格

在 Django 模板代码中遵循以下规则。

- `{% extends %}` 应该是第一个非注释行。

  这样做：

  ```html+django
  {% extends "base.html" %}

  {% block content %}
    <h1 class="font-semibold text-xl">
      {{ pages.title }}
    </h1>
  {% endblock content %}
  ```

  或者这样：

  ```html+django
  {# This is a comment #}
  {% extends "base.html" %}

  {% block content %}
    <h1 class="font-semibold text-xl">
      {{ pages.title }}
    </h1>
  {% endblock content %}
  ```

  不要这样做：

  ```html+django
  {% load i18n %}
  {% extends "base.html" %}

  {% block content %}
    <h1 class="font-semibold text-xl">
      {{ pages.title }}
    </h1>
  {% endblock content %}
  ```
- 在 `{{`、变量内容和 `}}` 之间只放一个空格。

  这样做：

  ```html+django
  {{ user }}
  ```

  不要这样做：

  ```html+django
  {{user}}
  ```
- 在 `{% load ... %}` 中，按字母顺序列出库。

  这样做：

  ```html+django
  {% load i18n l10 tz %}
  ```

  不要这样做：

  ```html+django
  {% load l10 i18n tz %}
  ```
- 在 `{%`、标签内容和 `%}` 之间只放一个空格。

  这样做：

  ```html+django
  {% load humanize %}
  ```

  不要这样做：

  ```html+django
  {%load humanize%}
  ```
- 如果 `{% block %}` 标签名称不在同一行，则在 `{% endblock %}` 标签中放入 `{% block %}` 标签名称。

  这样做：

  ```html+django
  {% block header %}

    Code goes here

  {% endblock header %}
  ```

  不要这样做：

  ```html+django
  {% block header %}

    Code goes here

  {% endblock %}
  ```
- 在大括号内，除了属性访问的 `.` 和过滤器的 `|` 周围外，用单个空格分隔标记。

  这样做：

  ```html+django
  {% if user.name|lower == "admin" %}
  ```

  不要这样做：

  ```html+django
  {% if user . name | lower  ==  "admin" %}

  {{ user.name | upper }}
  ```
- 在使用 `{% extends %}` 的模板中，避免缩进顶级 `{% block %}` 标签。

  这样做：

  ```html+django
  {% extends "base.html" %}

  {% block content %}
  ```

  不要这样做：

  ```html+django
  {% extends "base.html" %}

    {% block content %}
    ...
  ```

## 视图风格

- 在Django的视图中，第一个参数应该总是 `request` 。

  这样做：

  ```
  def my_view(request, foo): ...
  ```

  别这样做：

  ```
  def my_view(req, foo): ...
  ```

## 模型风格

- 字段名称应当全部使用小写，使用下划线替代驼峰命名。

  这样做：

  ```
  class Person(models.Model):
      first_name = models.CharField(max_length=20)
      last_name = models.CharField(max_length=40)
  ```

  别这样做：

  ```
  class Person(models.Model):
      FirstName = models.CharField(max_length=20)
      Last_Name = models.CharField(max_length=40)
  ```
-  `class Meta` 类应该位于定义字段之后，用一个空行分割字段定义和类定义。

  这样做：

  ```
  class Person(models.Model):
      first_name = models.CharField(max_length=20)
      last_name = models.CharField(max_length=40)

      class Meta:
          verbose_name_plural = "people"
  ```

  别这样做：

  ```
  class Person(models.Model):
      class Meta:
          verbose_name_plural = "people"

      first_name = models.CharField(max_length=20)
      last_name = models.CharField(max_length=40)
  ```
- 模型内的类核方法的定义应该遵循以下顺序（不是所有项都是必须的）：

  - 所有数据库字段
  - 自定义管理器属性
  - `Meta 类`
  - `def __str__()` and other Python magic methods
  - `save() 方法`
  - `get_absolute_url() 方法`
  - 其他自定义方法
- 如果为给定的模型字段定义了 `choices`，请将每个选择定义为一个映射，使用全大写名称作为模型上的类属性。示例：

  ```
  class MyModel(models.Model):
      DIRECTION_UP = "U"
      DIRECTION_DOWN = "D"
      DIRECTION_CHOICES = {
          DIRECTION_UP: "Up",
          DIRECTION_DOWN: "Down",
      }
  ```

  或者，考虑使用 [枚举类型](/zh-hans/5.2/ref/models/fields/#field-choices-enum-types):

  ```
  class MyModel(models.Model):
      class Direction(models.TextChoices):
          UP = "U", "Up"
          DOWN = "D", "Down"
  ```

## `django.conf.settings` 配置的使用

通常，模块不应使用存储在顶层的配置 `django.conf.settings` 配置（即在导入模块时进行评估）。 对此的解释如下：

手动配置设置（即不依赖于 [`DJANGO_SETTINGS_MODULE`](/zh-hans/5.2/topics/settings/#envvar-DJANGO_SETTINGS_MODULE) 环境变量）是允许的，并且可以按以下方式进行：

```
from django.conf import settings

settings.configure({}, SOME_SETTING="foo")
```

但是，如果在配置 `settings.configure` 行配置之前访问了任何设置，则此操作将无效。（在内部，`settings` 配置是一个 `LazyObject` ，当尚未访问设置时，它会自动配置自己）。

因此，如果有一个包含一些代码的模块，如下所示：

```
from django.conf import settings
from django.urls import get_callable

default_foo_view = get_callable(settings.FOO_VIEW)
```

…然后导入此模块将导致设置对象被配置。 这意味着第三方在顶层导入模块的能力与手动配置设置对象的能力不兼容，或者在某些情况下使其变得非常困难。

为了替换上面的代码，必须使用惰性级别或间接级别，例如 `django.utils.functional.LazyObject` ， `django.utils.functional.lazy()` 或者 `lambda` 。

## 杂项

- 为国际化标记所有的字符串；更多细节请参见 [i18n documentation](/zh-hans/5.2/topics/i18n/)。
- 在更改代码时，删除不再使用的 `import` 语句。[flake8](https://pypi.org/project/flake8/) 将为你识别这些未使用的导入。如果需要保留一个未使用的导入以保持向后兼容性，请在末尾标记为 `# NOQA` 以消除 flake8 的警告。
- 按部就班的删除代码结尾那些增加不必要字节的空格，增加了显示混乱还可能导致偶尔的合并冲突。一些IDE可以通过配置自动删除它们，大多数版本控制工具可以在差异比较时高亮显示它们。
- 请不要在你贡献的代码里面放入你的名字，我们的政策是把贡献者名字保存到随Django分发的AUTHORS文件里--—而不是分散在代码库里。如果你进行的不只是一次简单修改，请随意更改在你补丁中的AUTHORS文件。

## JavaScript 样式

关于 Django 所使用 JavaScript 代码样式的更多内容，请参见 [JavaScript 代码](/zh-hans/5.2/internals/contributing/writing-code/javascript/)。
