程式碼風格

在撰寫程式碼以納入 Django 時,請遵循這些程式碼標準。

Pre-commit 檢查

pre-commit 是一個用於管理 pre-commit 鉤子的框架。這些鉤子有助於在提交程式碼以進行審閱之前識別簡單的問題。在程式碼審閱之前檢查這些問題,可以讓審閱者專注於變更本身,也有助於減少 CI 的執行次數。

要使用此工具,請先安裝 pre-commit,然後安裝 git 鉤子

$ python -m pip install pre-commit
$ pre-commit install
...\> py -m pip install pre-commit
...\> pre-commit install

在第一次提交時,pre-commit 將會安裝這些鉤子,這些鉤子會安裝在各自的環境中,並且在第一次執行時需要一些時間安裝。後續的檢查將會顯著加快。如果發現錯誤,將會顯示適當的錯誤訊息。如果錯誤出在 blackisort,則工具將會自動為您修正。如果您對這些變更感到滿意,請檢視這些變更並重新暫存以進行提交。

Python 風格

  • 所有檔案都應該使用 black 自動格式化程式進行格式化。如果已設定 pre-commit,則會執行此程式。

  • 專案儲存庫包含一個 .editorconfig 檔案。我們建議使用支援 EditorConfig 的文字編輯器,以避免縮排和空白的問題。Python 檔案使用 4 個空格進行縮排,而 HTML 檔案使用 2 個空格。

  • 除非另有指定,否則請遵循 PEP 8

    使用 flake8 來檢查此區域中的問題。請注意,我們的 .flake8 檔案包含一些排除的檔案(我們不關心清理的已棄用模組,以及 Django 供應的一些第三方程式碼),以及一些我們不認為是嚴重違規的排除錯誤。請記住,PEP 8 只是一個指南,因此請將尊重周圍程式碼的風格作為主要目標。

    PEP 8 的一個例外是我們關於程式碼行長度的規則。如果程式碼看起來明顯更醜陋或更難以閱讀,請不要將程式碼行限制為 79 個字元。我們允許最多 88 個字元,因為這是 black 使用的行長度。當您執行 flake8 時,會包含此檢查。即使 PEP 8 建議為 72 個字元,文件、註解和文件字串也應包裝在 79 個字元處。

  • 字串變數插值可以使用 %-formattingf-stringsstr.format(),以最大化程式碼可讀性為目標。

    可讀性的最終判斷由合併者自行決定。作為指南,f 字串應僅使用簡單的變數和屬性存取,對於更複雜的情況,請事先進行本機變數指派。

    # 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 字串不應該用於任何可能需要翻譯的字串,包括錯誤和記錄訊息。一般來說,format() 更詳細,因此其他格式化方法是首選。

    不要浪費時間對現有程式碼進行不相關的重構,以調整格式化方法。

  • 在註解中避免使用「我們」,例如「Loop over」而不是「We loop over」。

  • 對於變數、函式和方法名稱,請使用底線,而不是 camelCase (即 poll.get_unique_voters(),而不是 poll.getUniqueVoters())。

  • 對於類別名稱 (或傳回類別的工廠函式),請使用 InitialCaps

  • 在文件字串中,請遵循現有文件字串和 PEP 257 的風格。

  • 在測試中,請使用 assertRaisesMessage()assertWarnsMessage(),而不是 assertRaises()assertWarns(),這樣您就可以檢查例外或警告訊息。只有在需要規則運算式比對時,才使用 assertRaisesRegex()assertWarnsRegex()

    使用 assertIs(…, True/False) 來測試布林值,而不是 assertTrue()assertFalse(),這樣您就可以檢查實際的布林值,而不是運算式的真值。

  • 在測試文件字串中,說明每個測試所示範的預期行為。請勿包含「測試」或「確保」等前言。

    保留票證參考用於票證包含文件字串或註解中無法輕易描述的其他詳細資訊的模糊問題。將票證編號包含在句子的末尾,如下所示

    def test_foo():
        """
        A test docstring looks like this (#123456).
        """
        ...
    

匯入

  • 使用 isort 來自動化匯入排序,並使用以下指南。

    快速開始

    $ python -m pip install "isort >= 5.1.0"
    $ isort .
    
    ...\> py -m pip install "isort >= 5.1.0"
    ...\> isort .
    

    這會從您目前目錄遞迴執行 isort,並修改任何不符合指南的檔案。如果需要讓匯入順序不正確 (例如,避免循環匯入),請使用如下的註解

    import module  # isort:skip
    
  • 將匯入置於這些群組中:future、標準程式庫、第三方程式庫、其他 Django 元件、本機 Django 元件、try/excepts。按完整模組名稱依字母順序排序每個群組中的行。在每個區段中,將所有 import module 陳述式放在 from module import objects 之前。對於其他 Django 元件,請使用絕對匯入,對於本機元件,請使用相對匯入。

  • 在每一行上,按字母順序排列項目,並將大寫項目分組在小寫項目之前。

  • 使用括號斷開長行,並將連續行縮排 4 個空格。在最後一個匯入後包含一個尾隨逗號,並將右括號放在其自己的行上。

    在最後一個匯入和任何模組層級程式碼之間使用單一空白行,並在第一個函式或類別上方使用兩個空白行。

    例如 (註解僅用於解釋目的)

    django/contrib/admin/example.py
    # 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 %} 應該是第一個非註解行。

    執行此操作

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

    或此操作

    {# This is a comment #}
    {% extends "base.html" %}
    
    {% block content %}
      <h1 class="font-semibold text-xl">
        {{ pages.title }}
      </h1>
    {% endblock content %}
    

    不要執行此操作

    {% load i18n %}
    {% extends "base.html" %}
    
    {% block content %}
      <h1 class="font-semibold text-xl">
        {{ pages.title }}
      </h1>
    {% endblock content %}
    
  • {{、變數內容和 }} 之間放置一個空格。

    執行此操作

    {{ user }}
    

    不要執行此操作

    {{user}}
    
  • {% load ... %} 中,依字母順序排列程式庫。

    執行此操作

    {% load i18n l10 tz %}
    

    不要執行此操作

    {% load l10 i18n tz %}
    
  • {%、標籤內容和 %} 之間放置一個空格。

    執行此操作

    {% load humanize %}
    

    不要執行此操作

    {%load humanize%}
    
  • 如果 {% block %} 標籤名稱不在同一行,則將其放在 {% endblock %} 標籤中。

    執行此操作

    {% block header %}
    
      Code goes here
    
    {% endblock header %}
    

    不要執行此操作

    {% block header %}
    
      Code goes here
    
    {% endblock %}
    
  • 在大括號內,用單一空格分隔符號,但在屬性存取的 . 和篩選器的 | 周圍除外。

    執行此操作

    {% if user.name|lower == "admin" %}
    

    不要執行此操作

    {% if user . name | lower  ==  "admin" %}
    
    {{ user.name | upper }}
    
  • 在使用 {% extends %} 的範本內,避免縮排最上層的 {% block %} 標籤。

    執行此操作

    {% extends "base.html" %}
    
    {% block content %}
    

    不要執行此操作

    {% extends "base.html" %}
    
      {% block content %}
      ...
    

檢視風格

  • 在 Django 檢視中,檢視函式中的第一個參數應稱為 request

    執行此操作

    def my_view(request, foo): ...
    

    不要執行此操作

    def my_view(req, foo): ...
    

模型風格

  • 欄位名稱應全部小寫,使用底線而不是 camelCase。

    執行此操作

    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)
    
  • 模型內部類別和標準方法的順序應如下(請注意,並非全部都是必需的):

    • 所有資料庫欄位

    • 自訂管理器屬性

    • class Meta

    • def __str__()

    • def save()

    • def get_absolute_url()

    • 任何自訂方法

  • 如果為給定的模型欄位定義了 choices,請將每個選擇定義為一個映射,並在模型上使用全大寫名稱作為類別屬性。範例:

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

    或者,考慮使用 列舉類型

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

使用 django.conf.settings

模組通常不應在頂層使用儲存在 django.conf.settings 中的設定(即在模組匯入時評估)。原因如下:

允許且可以手動配置設定(即不依賴 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.LazyObjectdjango.utils.functional.lazy()lambda

其他

  • 標記所有字串以進行國際化;請參閱 i18n 文件 以取得詳細資訊。

  • 當您變更程式碼時,請移除不再使用的 import 陳述式。flake8 將為您識別這些匯入。如果為了向後相容性需要保留未使用的匯入,請在結尾標記 # NOQA 以關閉 flake8 警告。

  • 請系統地移除程式碼中的所有尾隨空格,因為它們會增加不必要的位元組、在修補程式中增加視覺混亂,並且偶爾會導致不必要的合併衝突。可以將某些 IDE 設定為自動移除它們,並且可以將大多數 VCS 工具設定為在差異輸出中反白顯示它們。

  • 請不要將您的姓名放在您貢獻的程式碼中。我們的政策是將貢獻者的姓名保留在 Django 隨附的 AUTHORS 檔案中,而不是散落在程式碼庫本身中。如果您進行的不只是一個簡單的變更,請隨意在您的修補程式中包含對 AUTHORS 檔案的變更。

JavaScript 樣式

有關 Django 使用的 JavaScript 程式碼樣式的詳細資訊,請參閱 JavaScript 程式碼

返回頂部