江修
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Python 程式碼品質管理工具 - Astral Ruff ## 1. Ruff 介紹 Ruff 是 [Astral](https://github.com/astral-sh) 團隊開發的一個 Python 的程式碼檢查工具,整合了 Lint、Formatter、Import 排序(isort) 的工具,並且透過 Rust 實作,速度飛快。(對一般小型專案開發,速度差異可能不明顯,但對大型專案或 CI/CD pipeline,能大幅縮短檢查時間) ### 核心功能 | 工具 | 特點 | 備註 | | -------- | -------- | -------- | | flake8 | 經典 Python Linter <font color="orange">*[1]*</font> | 需多個 plugin 才能完整檢查 | | black | Formatter | 只能排版,不檢查 lint | | isort | Import 排序 | 只排序 import | | **Ruff** | **一次整合所有功能** | **高速、可替代 flake8 + black + isort** | > <font color="orange">[1]</font>: Python Linter是一個靜態程式碼分析工具,在不執行程式的情況下,自動檢查程式碼中的語法錯誤、潛在漏洞、不規範的編程風格(Ex: PEP 8)以及不一致的縮排。 ## 2. 為何需要? ### 2-1. 關於團隊協作開發 在團隊協作開發中,每個人的程式撰寫習慣不同,容易導致: - 程式碼風格不一致 - 潛在 bug 難以及時發現 - PR review 時浪費時間在格式問題上 雖然單獨使用 flake8、black 或 isort 也能達成部分檢查或排版功能,但: - 需要多個工具組合才能完整覆蓋 lint + formatter + import 排序 - 執行速度慢,對大型專案或 CI pipeline 影響明顯 因此,選擇一個整合 Lint、Formatter、Import 排序、且速度極快的工具,對提升開發效率、統一團隊程式碼品質,是更好、CP值更高的實務做法。 ### 2-2. 關於 CI/CD workflow 在建立自動化工作流時,Coding Style 還只是冰山一角,但不能沒有它。 一套穩固的 CI/CD pipeline,最終目標是讓程式碼從開發到部署都能自動化、可預期、可追蹤。但這一切的前提是**程式碼本身要有一致的品質基準**,否則自動化只是把混亂加速。 Ruff 這類工具扮演的角色,是在 pipeline 的最前端建立第一道防線。當每一個 PR 進來,透過 CI 可以先確認: - 程式碼風格是否一致 - 是否有明顯的潛在問題 - import 是否整潔有序 這些檢查通過後,後續的 test、build、deploy 才有意義。若連基本的 Coding style 都無法統一,test 結果的可信度、build 的穩定性、甚至 code review 的效率都會受到影響。 - 對團隊而言: 導入 Ruff 並將其整合進 CI 的真正價值不只是「抓格式錯誤」,而是建立一個客觀的、自動執行的共同標準。 - 新成員加入時: 不需要靠口頭約定或人工提醒,規範本身就內建在流程裡,Clone 一個專案下來,透過設定檔定義,無需解釋。 - 長期好處: Codebase 保持可維護性、擴充性與降低技術債。 ## 3. 開始使用 ### 3-1. 安裝 推薦使用 [Astral uv](https://github.com/astral-sh/uv) 來對專案進行管理。uv 是一個以 Rust 語言開發的 Python 專案管理工具,比傳統 pip、virtualenv 等工具執行速度快上10-100x(官方提供之數據),且操非常之方便,具備了套件安裝、虛擬環境管理、Python 版本管理、切換版本..等功能,非常強大! 以下將透過 `uv` 指令進行操作,若不使用,可自行替換成 `pip`。 > 更多 uv 介紹,可參考另一篇文章 [Python 套件管理工具: Astral uv ](https://hackmd.io/@MV1MNu9pSWqPmTx9CRvHbA/rJJAiBpzWl) 初始化 uv ```bash uv init # 初始化一個新的 uv 專案 ``` 安裝 Ruff ```bash uv add ruff ``` ### 3-2. 指令應用 第一章有提到 Ruff 核心功能包括了 flake8 的 Linter、black 的 format 與 isort 的 import 排序,我們一一對照: | 工具 | 對應 Ruff 指令 | 備註 | | -------- | -------- | -------- | | flake8 | `ruff check` | 內建大量 plugin 規則(pyflakes、pycodestyle、isort 等) | | black | `ruff format` | 格式化行為與 Black 高度相容 | | isort | `ruff check --select I` | isort 規則內建在 check 裡,用 I 系列規則 | 以往需要裝三個工具、跑三個指令,現在 Ruff 一次搞定,且速度快非常多。 #### 3-2-1. ruff check `ruff check` - 程式碼靜態分析 ```bash ruff check . # 檢查當前目錄 ruff check . --fix # 檢查後自動修正可修復的問題 ruff check . --watch # 監聽模式,檔案變更時自動重新檢查 ``` 假設程式碼中包含了未使用的 import 與未使用的變數 ![image](https://hackmd.io/_uploads/rJJeudEFbl.png =500x) 透過 `ruff check .` 會列出所有警告的位置與原因 ```bash xiu@MacBook-Air test-uv % uv run ruff check . F401 [*] `fastapi.FastAPIError` imported but unused --> main.py:2:30 | 1 | import uvicorn 2 | from fastapi import FastAPI, FastAPIError, HTTPException | ^^^^^^^^^^^^ 3 | from pydantic import BaseModel | help: Remove unused import: `fastapi.FastAPIError` F841 Local variable `unused_var` is assigned to but never used --> main.py:37:5 | 35 | @app.post("/items") 36 | def create_item(item: Item): 37 | unused_var = "imported but unused." | ^^^^^^^^^^ 38 | return {"created": item.name} | help: Remove assignment to unused variable `unused_var` Found 2 errors. [*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option). ``` 輸出中包含兩個警告,規則代碼前綴 `F` 代表來自 **Pyflakes**<font color="orange"> [2]</font> 規則集: | 規則代碼 | 說明 | 可自動修復 | |---------|------|----------| | `F401` | Import 了但從未使用的模組 | `--fix` 可自動移除 | | `F841` | 變數被賦值但從未使用 | 手動刪除 | 最後一行的提示說明了目前發現 2 個錯誤,其中 1 個可透過 `--fix` 自動修復;若加上 `--unsafe-fixes` 則可嘗試修復另外標示為不安全的項目 (Ruff 不確定修復後是否會改變程式行為) > <font color="orange">[2]</font>「**Ruff supports over 900 lint rules, many of which are inspired by popular tools like Flake8, isort, pyupgrade, and others.** Regardless of the rule's origin, Ruff re-implements every rule in Rust as a first-party feature.」 > Ruff 整合了不同程式碼檢查工具的錯誤規則,更多可以參考 [官方文件: Ruff/Rules](https://docs.astral.sh/ruff/rules/) >> 也可參考本文 3-3. 章節中 [常用的 Ruff 規則來源分類](#常用的-Ruff-規則來源分類) #### 3-2-2. ruff check --select I `ruff check --select I` - Import 排序檢查(isort) 假設程式碼包含了許多 import 套件 ![image](https://hackmd.io/_uploads/S1i1C_Etbx.png =500x) isort 會將 import 依來源分成三個區塊,區塊之間以空行分隔: ```python # 第一區塊:標準函式庫 import json import logging import os from datetime import datetime from typing import Dict, List, Optional, Tuple, Union # 第二區塊:第三方套件 import uvicorn from fastapi import Depends, FastAPI, HTTPException, Query from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse # 第三區塊:本地模組 from myapp.database import get_user, list_users from myapp.models import Item, UserResponse from myapp.utils import get_pagination, verify_token ``` 上圖範例中,原本的 import 順序混亂(第三方與標準函式庫交錯),透過 `--fix` 修正後,Ruff 會自動依照上述規則重新排列,並在不同區塊之間補上空行。 > `--select I` 表示只啟用 `I` 系列規則(isort),不執行其他 lint 檢查。若平常已在 `pyproject.toml` 中設定 `select = ["I"]`,則直接執行 `ruff check . --fix` 即可。 > 更多可參考下一個章節 [自定義規則](#3-3-自定義規則) #### 3-2-3. ruff format `ruff format` - 程式碼格式化工具 ```bash ruff format . # 格式化當前目錄所有檔案 ruff format --check . # 僅檢查格式,不實際修改(常用於 CI Workflow) ruff format --diff . # 顯示格式化前後的差異 ``` `ruff format` 設計目標是作為 Black 的直接替代品,預設配置與 Black 一致: | 項目 | 預設值 | | ------ | -------- | | 行長度 | `88` 字元 | | 字串引號 | 雙引號 `"` | | 縮排 | 4 個空格 | | 換行符號 | LF (`\n`) | | Magic Trailing Comma | 啟用(有尾隨逗號時自動展開換行) | ### 3-3. 自定義規則 透過 pyproject.toml 可以自訂義 Formatter 規則,並以不同區塊區分設定。 #### 3-3-1. [tool.ruff] 全域設定 ```toml [tool.ruff] line-length = 79 # 每行最大字數 (以 PEP8 規範為例, 79 個字元) indent-width = 4 # 縮排寬度 target-version = "py311" # 目標 Python 版本 extend-exclude = [".git", ".venv", "logs/"] # 忽略的檔案或目錄 ``` - `line-length`:每行最大字元數,可依團隊或專案自行決定,PEP 8 建議為 79,Black 預設為 88,亦可依照團隊或專案自行設定。 - `target-version`:指定目標 Python 版本,會影響 `UP`(pyupgrade)規則的判斷,例如設為 `py311` 時,Ruff 只會建議 Python 3.11 以上才支援的新語法 - `extend-exclude`:額外排除不需要檢查的目錄或檔案,通常會排除虛擬環境與版本控制目錄 #### 3-3-2. [tool.ruff.lint] 檢查規則設定 ```toml [tool.ruff.lint] select = ["E", "F", "I", "UP", "B", "N"] # 啟用的規則 ignore = ["E501"] # 忽略的規則 fixable = ["ALL"] # 允許自動修復的規則 unfixable = [] # 禁止自動修復的規則 # 允許在特定情況下使用的規則 per-file-ignores = { "tests/**" = ["F401"], "**/__init__.py" = ["F401"] } ``` `select` 中各規則集的對應說明 #### 常用的 Ruff 規則來源分類 | 代號 | 來源工具 | 說明 | | :--------: | -------- | -------- | | E | pycodestyle | PEP 8 風格規範 | | F | Pyflakes | 邏輯錯誤,如未使用的 import、未定義的變數 | | I | isort | Import 排序 | | UP | pyupgrade | 建議升級為新版 Python 語法 | | B | flake8-bugbear | 常見 bug 與設計問題 | | N | pep8-naming | 命名慣例(函式、類別、變數命名) | `per-file-ignores` 可針對特定路徑忽略特定規則,例如 `__init__.py` 通常會刻意 re-export 模組,因此忽略 `F401`(unused import)是合理的做法。 #### 自定義忽略警告 特定測試、`__init__` 或 migrations 目錄,可透過 `[tool.ruff.lint.per-file-ignores]` 針對路徑忽略特定規則 ```toml [tool.ruff.lint.per-file-ignores] "tests/**" = ["F401", "S101"] # 測試檔案忽略 unused import 與 assert "**/__init__.py" = ["F401"] # init 檔案忽略 unused import "migrations/**" = ["E501"] # migration 檔案忽略行長度 ``` 若只需要忽略 **單行** 的特定警告,可在該行尾端加上 `# noqa: [規則代號]` 註解 ```python QUERY_GET_USER = "SELECT id, name, email, created_at FROM users WHERE id = :user_id AND is_active = TRUE ORDER BY created_at DESC LIMIT 1;" # noqa: E501 ``` 兩種方法的適用情境: | 方法 | 適用情境 | |------|---------| | `per-file-ignores` | 整個目錄或檔案類型有系統性的例外(如 migrations、tests) | | `# noqa` | 單行的特殊情況,例如無法拆行的長字串、註解或 SQL 語法 | > 建議優先使用 `per-file-ignores` 集中管理例外規則,`# noqa` 僅用於真正無法避免的個案,避免濫用導致問題被掩蓋。 #### 3-3-3. [tool.ruff.format] 格式化設定 ```toml [tool.ruff.format] quote-style = "double" # 字串引號風格(double or single) indent-style = "space" # 縮排風格(space or tab) skip-magic-trailing-comma = false # 預設 False 若尾行有 ',' 則自動換行 line-ending = "lf" # 換行符號(lf / crlf / cr / native) 預設為 "lf" ``` format 排版規則中,通常建議配置: - `quote-style = "double"`:統一使用雙引號,與 Black 預設一致,也是 Python 社群較常見的方法。 - `indent-style = "space"`:使用空格縮排,符合 PEP8 建議,避免 tab 在不同編輯器顯示不一致的問題。 - `skip-magic-trailing-comma = false`:保留尾隨逗號的換行控制權,讓開發者可以刻意鎖定多行格式,git diff 也更乾淨。 - `line-ending = "lf"`:統一使用 Unix 換行符號,避免跨平台(Windows / macOS / Linux)協作時產生多餘的 diff。 > 以上四項均為 Ruff 的預設值,與 Black 的行為一致。若團隊無特殊需求,可省略不寫;明確寫出的好處是讓設定檔具備**自我說明**的效果,新成員一眼就能了解專案的格式規範。 ### 3-4. pyproject.toml 範例 ```toml [tool.ruff] line-length = 88 # 每行最大字元數(Black 預設值) indent-width = 4 # 縮排寬度 target-version = "py311" # 目標 Python 版本,影響 UP 規則的語法建議 extend-exclude = [".git", ".venv"] # 排除不需要檢查的目錄 [tool.ruff.lint] select = [ "E", # pycodestyle - PEP 8 風格規範 "F", # Pyflakes - 邏輯錯誤(unused import、未定義變數等) "I", # isort - Import 排序 "UP", # pyupgrade - 建議升級為新版 Python 語法 "B", # flake8-bugbear - 常見 bug 與設計問題 "N", # pep8-naming - 命名慣例 ] ignore = ["E501"] # 忽略行長度限制(由 line-length 統一管理) fixable = ["ALL"] # 允許所有規則自動修復 [tool.ruff.lint.per-file-ignores] "tests/**" = ["F401", "S101"] # 測試檔案忽略 unused import 與 assert "**/__init__.py" = ["F401"] # init 檔案忽略 unused import(re-export 用途) [tool.ruff.format] quote-style = "double" # 統一使用雙引號 indent-style = "space" # 使用空格縮排(非 tab) skip-magic-trailing-comma = false # 有尾隨逗號時強制保持多行 line-ending = "lf" # 統一使用 LF 換行(跨平台一致性) ``` ## 延伸閱讀 {%preview https://hackmd.io/@MV1MNu9pSWqPmTx9CRvHbA/rJJAiBpzWl %}

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully