跳到主要内容

16. 附录:交互模式的补充细节

对应官方文档:16. Appendix 一句话导读:把前面散落的交互模式小知识收成一页——报错怎么读、脚本怎么变成可执行命令、启动时自动跑点什么、以及全机器和单用户两种定制钩子。

附录不是新知识,更像说明书最后一页的小字:都是「平时用不上,一遇上就到处翻」的东西。官方这一章只有一个编号小节 16.1,下面挂四个子小节,村长严格按这个编号走。

16.1 交互模式(Interactive Mode)​

官方现在会告诉你:交互 REPL 其实有两种。

  • 经典基础解释器:所有平台都支持,只带最基本的行控制能力,也就是教程里那个朴素的 >>>。
  • 新交互 shell:从 Python 3.13 起成为默认,支持彩色输出、多行编辑、历史浏览和粘贴模式。

新 shell 多了几个功能键,值得记一下:

按键作用
F1进入交互帮助浏览器(也就是 pydoc)
F2浏览命令行历史,去掉 >>> / ... 提示符,也不带输出,方便复制
F3进入粘贴模式,粘一大段代码不容易被自动缩进搅乱;再按一次返回普通模式

退出方式也变了:新 shell 里直接敲 exit 或 quit 就行,不用加括号。想退回经典解释器,设个环境变量即可:

# 临时退回经典 REPL
PYTHON_BASIC_REPL=1 python3

16.1.1 错误处理(Error Handling)​

出错时解释器会打印错误消息和栈回溯(traceback)。接下来发生什么,取决于代码是从哪儿来的:

  • 交互模式:打印完回到主提示符 >>>,会话不断,你可以接着改。
  • 从文件 / 脚本读入:打印完 traceback,以非零退出码结束进程,好让 shell 或 CI 知道「这步挂了」。
  • 被 try 的 except 子句接住的异常不算错误,不会触发上面这套流程。
  • 少数错误无条件致命(解释器内部不一致、某些内存耗尽场景),同样非零退出。
  • 所有错误信息写到标准错误流 stderr,正常输出写到标准输出 stdout;日志采集和重定向要靠这个区分。
# 被 except 接住的异常不会让脚本非零退出
try:
1 / 0
except ZeroDivisionError as exc:
print("接住了:", exc)

print("脚本继续执行到这里")

用命令行看退出码最直观:

python3 -c 'print("正常")' ; echo "退出码:$?"
python3 -c '1 / 0' ; echo "退出码:$?"
import sys

print("这行走 stdout")
print("这行走 stderr", file=sys.stderr)

在主提示符或次提示符按中断字符(通常是 Ctrl-C,有的键盘是 Delete),会丢弃当前这行输入并回到主提示符;但如果命令正在执行时按中断,则抛出 KeyboardInterrupt 异常,它可以被 try 接住。

# 模拟一次运行中的 Ctrl-C:证明它能被正常捕获
try:
raise KeyboardInterrupt
except KeyboardInterrupt:
print("Ctrl-C 被 try 接住,程序没有崩")

村长划重点:报错行为和「代码从哪来」有关,别把交互模式里「没事,还能继续敲」的直觉带到脚本里——脚本一报错就退出了。提问时贴文字版 traceback(连最后一行一起),别发截图;也要分清 red 的 stderr 和 black 的 stdout,2> 重定向的就是前者。

16.1.2 可执行 Python 脚本(Executable Python Scripts)​

在 BSD 系的 Unix(含 macOS、Linux)上,Python 脚本可以像 shell 脚本一样直接执行,只要两件事:

  1. 文件头两个字符是 #!,第一行写成 #!/usr/bin/env python3;
  2. 给文件加上可执行位:chmod +x。

# 在 Python 里本来就是注释符,所以这一行不会破坏语法;但位置必须是文件最开头,前面空一行都不行。另外这一行要以 Unix 的 \n 结尾,有些平台不吃 Windows 的 \r\n。

#!/usr/bin/env python3
"""hello.py:加上可执行位后可以直接 ./hello.py 运行。"""
import sys

print("你好,我是可执行脚本")
print("解释器:", sys.executable)
chmod +x hello.py
./hello.py

为什么用 /usr/bin/env python3 而不是写死 /usr/bin/python3?env 会顺着当前 PATH 找解释器——配合第 12 章的虚拟环境,激活哪个 venv 就用哪个 Python,写死路径就把 venv 绕过去了。

Windows 没有「可执行位」这个概念:安装器会自动把 .py 关联到 python.exe,双击即可运行;若改成 .pyw 后缀,还会把那个黑色控制台窗口隐藏掉,适合做图形界面小程序。

村长划重点:两个最容易翻车的细节——#! 必须是文件前两个字符(前面不能有 BOM、空行、注释),且首行行尾必须是 \n。写完用 python3 -m pip 系列命令调试没问题、但 ./hello.py 提示「bad interpreter」,八成就是从 Windows 拷过来带了 \r\n。

16.1.3 交互式启动文件(The Interactive Startup File)​

每次进 >>> 都要先 import 一堆东西?把环境变量 PYTHONSTARTUP 指向一个文件,启动时就会自动执行。它跟 Unix shell 的 .profile 是同一个思路。

几个边界要记清:

  • 只在交互会话读取:跑脚本时不读;把 /dev/tty 显式指定为命令源时也不读(虽然那种情况其他方面像交互会话)。
  • 文件里的代码执行在交互会话同一个命名空间,因此里面 import 的模块、def 的函数,进 >>> 直接就能用。
  • 可以在里面改提示符 sys.ps1 / sys.ps2。
  • 想在脚本里复用这份启动文件,必须自己显式 exec。
# ~/.pythonrc:由 PYTHONSTARTUP 指向,只在交互模式自动执行
import os
import sys

try:
import readline
except ImportError:
pass # Windows 等平台没有 readline,跳过
else:
import rlcompleter

readline.parse_and_bind("tab: complete")

sys.ps1 = "村长> "
sys.ps2 = "..... "

# 顺带读一份当前目录下的项目级启动文件
if os.path.isfile(".pythonrc.py"):
exec(open(".pythonrc.py", encoding="utf-8").read())

print("启动文件已加载,Tab 补全与自定义提示符就绪")
# 写进 ~/.zshrc 或 ~/.bashrc,之后每次新终端都生效
export PYTHONSTARTUP="$HOME/.pythonrc"
python3

脚本里想显式加载,就照官方给的写法:

import os

filename = os.environ.get("PYTHONSTARTUP")
if filename and os.path.isfile(filename):
with open(filename, encoding="utf-8") as fobj:
exec(fobj.read())

PYTHONSTARTUP 只在原生解释器生效,IPython 有自己的 ~/.ipython/profile_default/startup/ 目录(第 14 章提过),别把配置写错地方。

16.1.4 定制模块(The Customization Modules)​

Python 还留了两个更底层的钩子:sitecustomize 和 usercustomize。它们属于 site 模块初始化流程,每次启动 Python 都会被导入,比 PYTHONSTARTUP 更早、影响面更广。

先找到用户级 site-packages 在哪:

import site

print("用户级 site-packages:", site.getusersitepackages())
print("全局 site-packages:", site.getsitepackages())
print("是否启用用户站点:", site.ENABLE_USER_SITE)
# 命令行里快速查同一个路径
python3 -c "import site; print(site.getusersitepackages())"

# 用 -s 临时禁用自动导入,排查「是不是定制模块搞的鬼」
python3 -s -c "import site; print(site.ENABLE_USER_SITE)"

把下面这份存成 usercustomize.py 丢进用户级目录,之后每次启动都会生效:

# usercustomize.py:放在用户级 site-packages 里,每次启动都会执行
import builtins


def clip(text, width=40):
return text if len(text) <= width else text[:width] + "..."


builtins.clip = clip

builtins.clip 因此在任何模块里都能直接调用,不用 import。两者的区别:

钩子放在哪谁来创建导入顺序怎么关掉
sitecustomize全局 site-packages机器管理员先导入-S 或 -s
usercustomize用户级 site-packages你自己后导入-s

加一条时间线帮你理清:-S 会整个跳过 site 初始化;site 跑完 → sitecustomize → usercustomize → (若进入交互模式)才轮到 PYTHONSTARTUP。

村长划重点:这两个钩子影响这台机器上的每一次 Python 启动,包括别人写的脚本、构建工具、CI。别往里塞业务逻辑或打印语句,最容易出现的灵异现象就是「我代码没改,怎么行为变了」。排查第一步永远是 python3 -s,能复现问题就说明锅在定制模块里。

本章小结​

  • 16.1 讲交互 REPL 的两种形态:经典解释器全平台可用;Python 3.13 起默认的新 shell 支持彩色、多行编辑、F1 帮助、F2 历史浏览、F3 粘贴模式,exit / quit 不用加括号,可用 PYTHON_BASIC_REPL=1 退回。
  • 16.1.1:出错打印消息与 traceback;交互模式回到主提示符,脚本模式非零退出;被 except 捕获的不算错误;错误信息走 stderr、正常输出走 stdout;Ctrl-C 在输入时丢弃当前行、在执行时抛 KeyboardInterrupt。
  • 16.1.2:#!/usr/bin/env python3 必须是文件头两个字符且以 \n 结尾,配 chmod +x 即可 ./hello.py;Windows 靠 .py / .pyw 关联,无可执行位。
  • 16.1.3:PYTHONSTARTUP 只在交互会话生效,与会话共用命名空间,可改 sys.ps1 / sys.ps2;脚本里要用必须自己 exec;IPython 不读它。
  • 16.1.4:sitecustomize(全局,管理员)与 usercustomize(用户级,自己)由 site 在每次启动时导入,分别可用 -S / -s 禁用;排查异常行为先试 python3 -s。