Python 脚本结构:从 shebang 到入口函数的实战整理

2026-06-02 25 预计阅读时间:1 分钟
来源:realpython.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:8 分钟

一个 Python 脚本随手写起来很容易,但写得"有结构"却常被忽略。shebang 行怎么写、import 分组顺序、常量放哪、入口函数怎么组织——这些细节看似琐碎,却直接影响脚本的可读性、可维护性和跨平台可移植性。下面逐项拆解,并给出一份可直接套用的模板。

shebang:让脚本自己声明解释器

shebang 是脚本第一行 #! 开头的声明,告诉操作系统用哪个解释器执行文件。Python 脚本推荐写法:

#!/usr/bin/env python3

而不是硬编码路径 #!/usr/bin/python3。原因很简单:env 会从当前用户的 PATH 中查找 python3,在虚拟环境、conda 环境、自定义安装路径下都能正确命中,硬编码路径则做不到。

实际操作中,加上 shebang 后还需要赋予执行权限:

chmod +x my_script.py
# 之后可以直接运行
./my_script.py

如果不加 shebang,就只能 python3 my_script.py 调用。两者功能一样,但 shebang 让脚本更像一个"可执行程序",在 Makefile、CI pipeline、cron 任务里调用更自然。

import 分组:三层顺序,每组之间空一行

Python 社区约定(PEP 8、Google Style、ruff 默认规则)将 import 分为三组,每组之间空一行:

  1. 标准库os, sys, json, pathlib
  2. 第三方库requests, numpy, click
  3. 本地模块 — 自己项目内的 my_utils, config
# 1. 标准库
import json
import os
import sys
from pathlib import Path

# 2. 第三方库
import click
import requests

# 3. 本地模块
from my_utils import format_output
from config import DEFAULT_TIMEOUT

每组内部,import xxx 排在 from xxx import yyy 前面,再按模块名字字母序排列。这不是审美偏好——ruff 的 isort 规则默认就按这个顺序检查和自动修复,不遵守会直接报 lint 错误。

常量:集中放在模块顶部,命名全大写

脚本里硬编码的数值、URL、超时秒数等,应该提取为模块级常量,放在 import 之后、业务逻辑之前。命名用 UPPER_SNAKE_CASE

DEFAULT_TIMEOUT = 30
MAX_RETRIES = 3
API_BASE_URL = "https://api.example.com"
OUTPUT_DIR = Path("results")

好处是修改配置时只改一处,而不是在代码里到处搜索魔法数字。ruff 的 flake8-eradicate 规则甚至能检测到未使用的常量,提醒你清理。

入口函数:用 main() + if __name__ == "__main__"

把核心逻辑放进 main() 函数,底部用标准守卫调用:

def main() -> None:
    args = parse_args()
    data = fetch_data(args.url, timeout=DEFAULT_TIMEOUT)
    result = process(data)
    write_output(result, OUTPUT_DIR)


if __name__ == "__main__":
    main()

为什么不只是裸写逻辑?两个直接收益:

  • 可测试:其他脚本或测试文件可以 import your_script 后直接调用 main() 或内部函数,不会触发执行。
  • 可复用:未来想把脚本改成一个库,main() 变成 API 入口,结构几乎不用动。

main() 的返回类型标注 -> None 不是必须的,但加上后 ruff 的类型检查更顺畅,也给读者明确信号:这个函数不返回有意义的结果,成功靠副作用(写文件、打印输出),失败靠异常或 sys.exit

ruff 一键格式化与检查

上面所有结构规则——import 顺序、常量命名、未使用变量——ruff 都能自动检查和修复。在项目根目录创建 pyproject.toml

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = ["I", "E", "W", "F", "UP"]
# I = isort (import排序)
# E/W = pycodestyle (格式)
# F = pyflakes (未使用变量等)
# UP = pyupgrade (现代Python语法)

然后运行:

# 检查所有问题
ruff check my_script.py

# 自动修复(import排序、格式等)
ruff check --fix my_script.py

# 格式化(类似black的风格)
ruff format my_script.py

养成习惯:写完脚本跑一次 ruff check --fix && ruff format,结构问题基本零残留。

可直接套用的完整模板

把上面所有规则合到一起,得到一份最小但完整的脚本骨架:

#!/usr/bin/env python3
"""Fetch data from API and save processed results to disk."""

# 1. 标准库
import json
import sys
from pathlib import Path

# 2. 第三方库
import requests

# 3. 本地模块
# (暂无)

# ── 常量 ──────────────────────────────────────────
API_BASE_URL = "https://api.example.com/data"
DEFAULT_TIMEOUT = 30
MAX_RETRIES = 3
OUTPUT_DIR = Path("results")


# ── 函数 ──────────────────────────────────────────
def fetch_data(url: str, timeout: int = DEFAULT_TIMEOUT) -> dict:
    """从远程 API 获取 JSON 数据。"""
    for attempt in range(1, MAX_RETRIES + 1):
        try:
            resp = requests.get(url, timeout=timeout)
            resp.raise_for_status()
            return resp.json()
        except requests.RequestException as exc:
            print(f"第 {attempt} 次请求失败: {exc}")
    sys.exit(1)


def process(data: dict) -> list[str]:
    """对原始数据做简单提取,返回关键字段列表。"""
    return [item["name"] for item in data.get("items", []) if "name" in item]


def write_output(lines: list[str], dest: Path) -> None:
    """将结果逐行写入目标文件。"""
    dest.parent.mkdir(parents=True, exist_ok=True)
    dest.write_text("\n".join(lines), encoding="utf-8")
    print(f"已写入 {dest},共 {len(lines)} 条")


# ── 入口 ──────────────────────────────────────────
def main() -> None:
    data = fetch_data(API_BASE_URL)
    result = process(data)
    write_output(result, OUTPUT_DIR / "names.txt")


if __name__ == "__main__":
    main()

使用方式:

# 1. 保存为 fetch_data.py
# 2. 安装依赖
pip install requests ruff

# 3. 赋予执行权限并运行
chmod +x fetch_data.py
./fetch_data.py

# 4. 检查结构合规
ruff check --fix fetch_data.py && ruff format fetch_data.py

这份模板覆盖了 shebang、import 三组分组、常量集中、main() 入口守卫,并且 ruff 一跑就能通过。下次写新脚本时,复制这个骨架再填充业务逻辑,比从空文件起步省心得多。

写脚本前的快速自检

每次提交脚本前,用这五条对照:

  • shebang:是否有 #!/usr/bin/env python3
  • import 顺序:标准库 / 第三方 / 本地,每组空一行?
  • 常量:魔法数字是否已提取为 UPPER_SNAKE_CASE 常量?
  • 入口:核心逻辑是否在 main() 里,底部有 if __name__ 守卫?
  • ruffruff checkruff format 是否零报错?

五条全过,脚本结构就基本到位了。剩下的才是真正有意思的部分——业务逻辑本身。


相关推荐