Python 縮排規則:用空白定義程式碼區塊

Python 最與眾不同的設計之一,就是用縮排(Indentation)取代大括號 {} 來定義程式碼區塊。這不是風格選擇,而是語法規則——縮排錯誤會直接讓程式無法執行。本文完整說明 Python 縮排的運作原理、4 個空格的 PEP 8 標準規範、巢狀縮排的層次結構,以及空格與 Tab 混用的危險,並以 iffordef 三種最常見情境示範正確用法,幫助你從第一行程式碼就建立正確習慣。

Cinematic dark alchemy forge with ancient stone steps descending into glowing nested chambers, each indented level illuminated by progressively deeper amber firelight, glowing rune code blocks carved into layered stone walls, deep dramatic shadows emphasizing the hierarchical depth of each indented tier, cinematic lighting, 16:9

什麼是 Python 縮排?

在 C、Java、JavaScript 等語言中,程式碼區塊用大括號 {} 包圍;但在 Python 中,縮排(向右移動的空白)就是區塊的邊界。冒號 : 開啟一個新區塊,接下來所有縮排相同層次的程式碼,都屬於這個區塊;一旦縮排退回,就代表區塊結束。這個設計讓 Python 程式碼在視覺上天然具有清晰的層次結構,也是 Python 「可讀性至上」哲學的體現。

可以把縮排想成煉金坊的巢狀熔爐:外層爐膛是函數,內層爐膛是 if 判斷,最裡層才是真正執行的反應。每往內一層,就代表「在前一個條件成立後,才執行這段操作」。縮排決定的不只是排版,而是程式的執行邏輯

核心語法:縮排與程式碼區塊

# === 基本規則:冒號後的下一行必須縮排 ===

# if 條件判斷:符合條件時執行縮排內的程式碼
price = 185.5
if price > 180:
    print("價格高於 180")   # 縮排 4 個空格,屬於 if 區塊
    print("達到進場條件")   # 同樣縮排 4 格,仍在 if 區塊內
print("這行沒有縮排")       # 退回原層,不屬於 if,一定會執行

# for 迴圈:每次迭代都執行縮排內的程式碼
closes = [183.5, 185.2, 187.8, 183.5, 188.1]
for c in closes:
    result = c * 1.02      # 縮排,在 for 區塊內
    print(result)          # 縮排,在 for 區塊內
print("迴圈結束")           # 無縮排,for 結束後才執行

# def 函數定義:函數主體必須縮排
def greet(name):
    message = f"Hello, {name}!"   # 縮排,函數主體
    return message                  # 縮排,函數主體

result = greet("離火煉金坊")
print(result)    # 無縮排,函數外部
Python 官方風格指南 PEP 8 規定:每個縮排層次使用 4 個空格(不是 1 個、2 個,也不是 Tab)。幾乎所有主流 IDE(VS Code、PyCharm)預設都會在你按下 Tab 鍵時自動插入 4 個空格,讓你無需手動敲 4 次空白鍵。
# === 巢狀縮排:多層區塊逐層往右 ===

def check_signal(price, rsi):
    """根據價格與 RSI 判斷交易訊號(巢狀縮排示範)。"""
    if price > 180:                    # 第一層縮排(4 格):在函數內
        if rsi < 30:                   # 第二層縮排(8 格):在外層 if 內
            print("超賣 + 高價:強烈買入訊號")
            return "BUY"               # 第二層縮排,仍在內層 if
        elif rsi > 70:                 # 第二層縮排,elif 與內層 if 對齊
            print("超買 + 高價:觀望")
            return "HOLD"
        else:                          # 第二層縮排,else 與內層 if 對齊
            print("RSI 中性,持倉")
            return "HOLD"
    else:                              # 第一層縮排,與外層 if 對齊
        print("價格低於門檻,不進場")
        return "SKIP"

# 呼叫函數(不縮排,在函數外)
signal = check_signal(185.5, 28)
print(f"訊號:{signal}")
巢狀縮排的關鍵是:同一層次的 ifelifelse 必須對齊(起始空格數完全相同),它們的程式碼主體再各自多縮排 4 格。層次愈深,程式碼愈往右移動。

規則與注意事項

規則 正確範例 錯誤範例
縮排量:4 個空格(PEP 8) if x:
    print(x)
if x:
  print(x)
(2 格,不一致就報錯)
冒號後必須縮排 def f():
    return 1
def f():
return 1
(IndentationError)
同層對齊 if a:
    pass
else:
    pass
if a:
    pass
  else:
(else 縮排錯誤)
不可混用 Tab 與空格 統一使用空格(IDE 設定 Tab = 4 spaces) 同一檔案 Tab + 空格混用(TabError)
空白行不影響縮排 函數間可加空白行分隔(PEP 8 建議 2 行) 在縮排區塊中間加入縮排不一致的行

常見錯誤與防呆

錯誤一:IndentationError — 縮排層次不一致

# ❌ 錯誤寫法(同一區塊縮排不一致)
# IndentationError: unexpected indent
def calc(x):
    result = x * 2
      return result     # 多了 2 格空白,與上行不對齊

# ✅ 正確寫法:同一區塊所有行用相同縮排量
def calc(x):
    result = x * 2
    return result       # 與上行同樣 4 格縮排

錯誤二:邏輯縮排錯誤 — print 放錯層次,輸出結果不符預期

# ❌ 邏輯錯誤:print 縮排在 for 內,每次迴圈都印一次
total = 0
closes = [183.5, 185.2, 187.8]
for c in closes:
    total += c
    print(f"目前總和:{total}")   # 印了 3 次!

# ✅ 正確做法:只想看最終結果,print 縮排退回 for 外
total = 0
for c in closes:
    total += c
print(f"最終總和:{total}")       # 迴圈全部跑完才印一次

錯誤三:TabError — Tab 與空格混用

# ❌ 危險:某行用 Tab,其他行用空格,視覺上看起來對齊,執行卻報錯
# TabError: inconsistent use of tabs and spaces in indentation
def bad_func():
	x = 1       # 這行用 Tab(→)縮排
    return x    # 這行用 4 個空格縮排,兩種混用!

# ✅ 解決方法:IDE 設定「Tab 自動轉換為 4 個空格」
# VS Code:settings → Editor: Tab Size = 4 + Insert Spaces = ✓
# PyCharm:Preferences → Code Style → Python → Tab size = 4 + Use tab character = ✗
def good_func():
    x = 1       # 統一 4 個空格
    return x    # 統一 4 個空格

進階用法

pass 佔位符:保持縮排結構完整

Python 規定縮排區塊不能為空——函數、iffor 的主體至少要有一行程式碼。開發量化策略時,常需要先定義函數骨架但尚未填入邏輯,這時用 pass 作為佔位符,讓程式碼合法執行而不報錯。

# pass:空區塊佔位,讓結構合法
def calculate_drawdown(equity_curve):
    pass    # TODO: 之後實作最大回撤計算

def on_tick(price):
    pass    # TODO: 之後實作即時策略邏輯

class Strategy:
    pass    # TODO: 之後補充類別方法

# if-else 骨架先建立,細節後填
signal = "BUY"
if signal == "BUY":
    pass   # TODO: 執行買入邏輯
elif signal == "SELL":
    pass   # TODO: 執行賣出邏輯
else:
    pass   # HOLD 不動作

長串列與字典的縮排慣例

當函數參數、串列、字典內容很多時,PEP 8 建議將每個元素獨立一行並縮排對齊,讓程式碼清晰易讀,不需要水平捲動。

# 多行串列:每個元素獨立一行,最後一個元素後加逗號(trailing comma)
ohlc_data = [
    [2024_01_02, 182.5, 185.9, 181.0, 185.2],
    [2024_01_03, 185.5, 188.2, 184.7, 187.8],
    [2024_01_04, 187.2, 188.5, 182.1, 183.5],    # trailing comma:方便之後新增行
]

# 多行字典
strategy_params = {
    "fast_period": 5,
    "slow_period": 20,
    "stop_loss_pct": 0.02,
    "take_profit_pct": 0.05,
}

# 多行函數呼叫:參數對齊開括號,或統一縮排 4 格
result = calculate_rsi(
    prices=closes,
    period=14,
    smooth=True,
)

量化策略中的多層縮排實戰

量化策略邏輯通常需要多層巢狀縮排:外層是迴圈遍歷 K 線,內層是條件判斷,最裡層是下單執行。清楚掌握縮排層次,才能確保策略邏輯按預期執行。

def run_backtest(ohlc_data, fast=5, slow=20):
    """
    簡易雙均線回測策略。
    外層:遍歷每根 K 線
    中層:判斷是否有足夠資料
    內層:判斷均線交叉方向
    """
    position = None    # 目前持倉狀態(None / 'LONG')
    trades = []        # 記錄所有交易

    for i in range(slow, len(ohlc_data)):   # 第一層:遍歷 K 線
        # 計算快慢均線
        fast_ma = sum(c[4] for c in ohlc_data[i-fast:i]) / fast
        slow_ma = sum(c[4] for c in ohlc_data[i-slow:i]) / slow
        close   = ohlc_data[i][4]

        if position is None:                # 第二層:未持倉時找進場
            if fast_ma > slow_ma:           # 第三層:快線上穿慢線
                position = 'LONG'
                trades.append({
                    'action': 'BUY',
                    'price': close,
                    'index': i,
                })
                print(f"買入:{close:.2f}")

        elif position == 'LONG':            # 第二層:持倉時找出場
            if fast_ma < slow_ma:           # 第三層:快線下穿慢線
                position = None
                trades.append({
                    'action': 'SELL',
                    'price': close,
                    'index': i,
                })
                print(f"賣出:{close:.2f}")

    return trades   # 退回函數第一層,回傳結果

煉金坊小叮嚀

縮排是 Python 的靈魂,但也是許多初學者第一個踩坑的地方。我自己剛開始時最常犯的錯誤,是把 returnprint 縮排放錯層次,結果函數提早結束或迴圈每次都印出結果。我的建議是:從一開始就在 IDE 設定「Tab 自動轉為 4 個空格」,並開啟「顯示空白符號」功能,這樣縮排問題一眼就看得出來。在撰寫量化策略時,我習慣先用 pass 把整個函數骨架建好,確認縮排層次正確後,再逐一填入邏輯——這樣能避免因為層次錯亂導致策略在錯誤的時機點執行,難以排查的 bug。記住:縮排不只是排版,它決定了你的程式碼「在什麼條件下」、「執行幾次」,是量化邏輯正確性的基石。

張貼留言

較新的 較舊