第一个 Python 脚本

要点

模块被导入时会执行顶层代码。name 判断帮助区分“直接运行入口”和“作为工具被导入”。

1. REPL:即时反馈的实验场

在项目目录执行 uv run python(或 uv run python -q 简化输出)进入交互式解释器:

>>> 2 ** 10
1024
>>> name = "python"
>>> name.upper()
'PYTHON'
>>> sorted("python")
['h', 'n', 'o', 'p', 't', 'y']
>>> help(str.replace)   # 随时查阅文档

上面是 REPL 会话记录(提示符与输出均属于交互式解释器),不是可直接保存为 .py 的脚本。

REPL 的高效用法:

特性说明
_保存上一次表达式的结果
Tab 补全自动补全属性与方法名
help(obj)查看对象文档
exit() 或 Ctrl+D退出

💡 REPL 适合验证”这一行能不能这么写”;一旦代码超过 10 行,就该写进脚本文件。


2. 第一个脚本

新建 hello.py:

"""练习脚手架:演示脚本结构与现代写法。"""

import sys

def greet(name: str, greeting: str = "Hello") -> str:
    """返回问候语。"""
    return f"{greeting}, {name}!"

def main(argv: list[str] | None = None) -> int:
    """程序入口:解析参数并打印问候。"""
    args = sys.argv[1:] if argv is None else argv
    target = args[0] if args else "World"
    print(greet(target))
    return 0  # 退出码,0 表示成功

if __name__ == "__main__":
    raise SystemExit(main())

运行:

uv run hello.py          # Hello, World!
uv run hello.py Python   # Hello, Python!
echo $?                  # 0(上一条命令的退出码)

逐点解析:

  • 模块 docstring:文件首行三引号字符串,help(hello) 时展示
  • 类型注解:name: str、-> str 为现代 Python 标配,编辑器据此提供提示
  • list[str] | None:3.10+ 直接用内置泛型和 | 联合类型,无需 typing.Optional
  • main 返回退出码:让脚本可被 shell 与 CI 判断成败

下面给出一个包含正常参数、缺省参数和不合规参数的完整版本。它仍使用“可导入的函数 + __main__ 入口”结构,避免把参数解析分散到模块顶层:

"""A small import-safe command-line greeting program."""

import sys

def greet(name: str, greeting: str = "Hello") -> str:
    return f"{greeting}, {name}!"

def main(argv: list[str] | None = None) -> int:
    args = sys.argv[1:] if argv is None else argv
    if len(args) > 2:
        print("usage: hello.py [name] [greeting]", file=sys.stderr)
        return 2
    name = args[0] if args else "World"
    greeting = args[1] if len(args) == 2 else "Hello"
    print(greet(name, greeting))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

运行 python -I hello.py Ada Hi 会输出 Hi, Ada! 并以 0 退出。传入三个以上参数时,它会把用法写到标准错误并以 2 退出;导入该文件不会自行打印或退出。


3. __main__ 惯用法详解

if __name__ == "__main__": 是 Python 最著名的惯用法,机制如下:

  1. Python 每个 .py 文件都是一个模块,模块有个字符串属性 __name__
  2. 直接运行某文件时,该文件的 __name__ 被设为 "__main__"
  3. 被 import 时,__name__ 等于模块名(如 "hello")
# hello.py 底部
print(f"__name__ = {__name__}")
if __name__ == "__main__":
    main()
uv run hello.py                 # __name__ = __main__(会执行 main)
uv run python -c "import hello" # __name__ = hello(不会执行 main)

为什么必须写它:没有这行,import hello 会立即执行 main()——测试框架、其他模块导入时都会”误触发”程序。加上守卫后,同一文件既能被导入复用,又能直接执行。


4. 脚本 vs 模块:组织雏形

当逻辑变多,把可复用部分拆为函数/类,__main__ 块只留”组装”:

# bookmarks.py 的典型骨架
def load(path: str) -> dict[str, str]:
    ...

def add(data: dict[str, str], title: str, url: str) -> None:
    ...

def main() -> int:
    data = load("bookmarks.json")
    add(data, "Python", "https://python.org")
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

这个骨架贯穿整个入门路径,第 08 篇的综合项目将扩展为命令行工具。


最佳实践

把可复用逻辑放函数中,入口负责解析参数、调用和返回退出码。main 判断避免模块被导入时就执行命令;main 是约定名称,不是解释器要求。

docstring 说明输入约束、结果和可能失败之处,避免复述函数名。用导入测试确认没有意外读写,再分别验证正常执行与失败时的退出状态,便于脚本和 CI 可靠调用。


常见问题

Q1: python hello.py 和 python -m hello 有区别吗?

A: 有。前者按路径运行;后者把 hello 当模块运行,会先把它所在目录加入 sys.path,且 __name__ 同样是 "__main__"。包内脚本推荐 -m 方式。

Q2: 为什么 uv run python 而不是直接 python?

A: uv run 保证使用项目 .venv 的解释器,直接 python 可能命中系统版本,依赖不一致。


练习与实践

练习一:改造脚本

  1. 给 greet 增加 punctuation: str = "!" 参数
  2. 支持 hello.py <name> <greeting>:第二个参数作为问候词
  3. 参数缺失时返回退出码 1 并打印用法提示

练习二:验证模块机制

  1. 在 main() 中加入 print(__name__)
  2. 分别用 uv run hello.py 与 uv run python -c "import hello" 观察 __name__
  3. 写一段话解释两种输出差异的原因

提示:差异来自 Python 导入系统对”主模块”的约定,与变量与类型中的对象属性机制一脉相承。


相关文档

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇