REPL 基础操作
REPL 是 Read-Eval-Print Loop(读取-求值-打印循环)的缩写,指 Python 解释器提供的交互式命令行环境。你输入一行代码,解释器立刻执行并把结果打印出来,再等待下一次输入——这个循环让 REPL 成为学习语法、验证想法、调试片段最直接的场所。
前置知识
阅读本节前,建议先了解:Python 版本生命周期
基础概念
在终端输入 python(Windows 上可能是 py),若未指定脚本文件,解释器会进入交互模式,显示主提示符 >>>:
$ python
Python 3.13.1 (main, Dec 3 2024, 10:00:00) [Clang 16.0.0] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>提示符有三种,各自含义不同:
| 提示符 | 含义 | 出现条件 |
|---|---|---|
>>> | 主提示符,等待新语句 | 循环处于就绪状态 |
... | 续行提示符,等待当前语句补全 | 上一行语法未结束 |
| 无提示符 | 正在执行,等待输出 | 语句正在运行 |
REPL 的三个阶段:
| 阶段 | 行为 |
|---|---|
| Read | 读取你输入的一行(或一个完整语句块) |
| Eval | 编译并执行这段代码 |
若不是 None,把表达式结果用 repr() 打印出来 |
理解「Print 使用 repr」很重要:在 REPL 中直接输入 'hello' 会显示带引号的 'hello',而 print('hello') 显示不带引号的 hello。前者展示对象的规范表示,后者展示面向人的字符串。
REPL 与「直接运行脚本」的关键差异:
| 维度 | REPL | 脚本文件 |
|---|---|---|
| 执行方式 | 逐条语句即时执行 | 整个文件从头到尾 |
| 结果输出 | 自动打印表达式结果 | 只有 print 等显式输出 |
| 状态 | 变量在会话内持续存在 | 每次运行重新开始 |
| 适用场景 | 探索、验证、调试 | 交付、自动化、复用 |
需要提醒的是:REPL 是「探索工具」,不是「写代码的地方」。在 REPL 里试通一段逻辑后,应及时复制到 .py 文件中保存,否则关闭终端就全部丢失。
语法与用法
启动 REPL 有多种方式:
# 直接启动(若系统同时装了 Python 2 和 3,需用 python3)
python
# 明确指定版本
python3.13
# 使用版本管理器提供的版本
pyenv shell 3.13.1 && python
uv run python
# 启动后立即执行一段代码,再进入交互模式(-i 的用途)
python -i script.py
# 只执行一段代码,不进入 REPL
python -c "print(1 + 1)"
# 启动时不加载 site 模块(可加快启动,但会缺少部分功能)
python -S退出 REPL 有几种方式:
# 方式一:内置函数(Python 3.13+ 可直接写 exit 或 quit,无需括号)
exit
quit
# 方式二:显式调用(所有版本通用)
exit()
quit()
# 方式三:键盘快捷键
Ctrl-D # Unix/macOS,发送 EOF
Ctrl-Z 回车 # Windows
# 方式四:导入 sys 后调用
import sys; sys.exit()在 REPL 中执行常见操作:
>>> 1 + 2 # 表达式结果自动打印
3
>>> name = "Python" # 赋值语句无输出
>>> name # 查看变量用 repr
'Python'
>>> print(name) # print 输出 str
Python
>>> _ # 下划线保存上一个表达式的结果
'Python'
>>> 3 * 7
21
>>> _ + 1 # 基于上一个结果继续计算
22
>>> type(1 + 2)
<class 'int'>多行语句会触发续行提示符:
>>> for i in range(3):
... print(i)
...
0
1
2
>>> def add(a: int, b: int) -> int:
... """返回两数之和。"""
... return a + b
...
>>> add(3, 4)
7注意代码块结束后需要输入一个空行,REPL 才知道语句块已经输完。
使用内置帮助工具:
>>> help(str) # 查看类型的文档(进入分页浏览,按 q 退出)
>>> help(str.split) # 查看具体方法
>>> dir(str) # 列出类型的属性与方法名
>>> str.split? # 在 IPython/Jupyter 中才有此语法,原生 REPL 不支持详细说明
REPL 的执行流程
输入 >>> print(1 + 1) 后,解释器内部依次做了这些事:
- 读取这一行文本;
- 用
compile(..., mode='single')尝试编译; - 若抛出
SyntaxError且原因是「输入不完整」,则显示...继续读下一行; - 编译成功后执行字节码;
- 若编译模式为
single且结果不是None,用sys.displayhook打印其repr。
sys.displayhook 是可以替换的,这也是 IPython 等增强型 REPL 显示富文本输出的原理。
>>> import sys
>>> sys.displayhook
<built-in function displayhook>
# 查看提示符变量(交互模式下才存在)
>>> sys.ps1
'>>> '
>>> sys.ps2
'... 'sys.ps1/sys.ps2 只在交互模式下定义,脚本中访问会抛 AttributeError。修改它们可以自定义提示符(见 启动文件与配置)。
判断代码是否运行在 REPL 中
有些代码需要区分「交互运行」与「脚本运行」。常用手段:
| 方式 | 说明 |
|---|---|
sys.flags.interactive | 是否为 -i 启动的交互模式 |
hasattr(sys, "ps1") | 是否处于真正的交互式 REPL |
__name__ == "__main__" | 是否作为主程序运行(不区分交互/脚本) |
sys.ps1 访问 | 仅在交互模式存在,否则抛异常 |
其中 hasattr(sys, "ps1") 是最可靠的「是否在 REPL 中」判断:
import sys
def in_repl() -> bool:
"""判断当前是否运行在交互式解释器中。"""
return hasattr(sys, "ps1")
if __name__ == "__main__":
if in_repl():
print("正在 REPL 中运行")
else:
print("正在以脚本方式运行")python -i:执行后进入 REPL
-i 参数的用途是:先运行指定脚本,运行结束后不退出,而是进入交互模式,并保留脚本中定义的所有变量。
# run_then_probe.py
python -i run_then_probe.py# run_then_probe.py
def load_config(path: str) -> dict[str, str]:
"""模拟加载配置,返回键值对。"""
return {"host": "localhost", "port": "8000"}
config = load_config("app.toml")
print("配置加载完成,可在 REPL 中查看变量 config")运行后进入 >>>,可以直接输入 config 查看结果,非常适合作「加载数据后手工探查」的调试方式。脚本异常退出时也会保留现场,便于检查出错前的状态。
python -c:单行执行
-c 适合在 Shell 脚本中嵌入简短逻辑,不进入 REPL:
# 打印版本号
python -c "import sys; print(sys.version)"
# 使用命令行参数(注意 sys.argv[0] 为 -c)
python -c "import sys; print(sys.argv[1:])" alpha beta
# ['alpha', 'beta']
# 计算并输出
python -c "print(sum(range(101)))"
# 5050-c 后可以跟多条语句,用分号或换行分隔;但它不适合放复杂逻辑——超过一两行就该写成脚本文件。
常用魔术变量与对象
原生 REPL 提供几个便利对象:
| 名称 | 含义 |
|---|---|
_ | 上一个表达式的结果 |
__name__ | 交互模式下为 "__main__" |
__doc__ | 当前模块的文档字符串 |
help | _sitebuiltins._Helper 实例 |
exit / quit | _sitebuiltins.Quitter 实例 |
_ 只在交互模式下由 sys.displayhook 自动赋值:
>>> 10 / 4
2.5
>>> round(_, 0)
2.0
>>> _ * 2
4.0注意 _ 不会在脚本中自动赋值,它是交互模式的专属便利。
用 dir() 与 help() 探索对象
REPL 最大的价值之一是「就地探索」。不必离开终端去查文档:
>>> import json
>>> dir(json) # 列出模块公开成员
['JSONDecodeError', 'JSONDecoder', ..., 'dumps', 'loads']
>>> help(json.loads) # 查看函数签名与文档
Help on function loads in module json:
loads(s, *, cls=None, ...)
>>> json.dumps({"a": 1}, ensure_ascii=False)
'{"a": 1}'
>>> s = " hello "
>>> dir(s) # 实例也可列出可用方法
>>> help(s.strip)对于自带文档字符串的函数,help() 会直接展示。若文档是分页显示的,按 q 退出、空格翻页、回车下一行。
实战示例
示例一:用 REPL 快速验证字符串处理
假设要从一段日志中提取时间戳,先在 REPL 中反复试验,确认正则可行后再落盘。
>>> line = "2024-10-07 12:34:56 ERROR failed to connect"
>>> parts = line.split(maxsplit=1)
>>> parts
['2024-10-07', '12:34:56 ERROR failed to connect']发现 split 只能切一次,改用正则:
>>> import re
>>> pattern = re.compile(r"^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.*)$")
>>> match = pattern.match(line)
>>> match.groups()
('2024-10-07', '12:34:56', 'ERROR', 'failed to connect')
>>> # 验证不匹配时的行为
>>> pattern.match("invalid line") is None
True确认无误后归档为脚本:
# parse_log.py
import re
LOG_PATTERN = re.compile(r"^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.*)$")
def parse_line(line: str) -> dict[str, str] | None:
"""解析单行日志,失败返回 None。"""
match = LOG_PATTERN.match(line)
if match is None:
return None
date, time_, level, message = match.groups()
return {"date": date, "time": time_, "level": level, "message": message}
if __name__ == "__main__":
sample = "2024-10-07 12:34:56 ERROR failed to connect"
print(parse_line(sample))这正是 REPL 的典型用法:先在交互环境试错,再把稳定的逻辑写成函数并加类型标注。
示例二:用 python -i 调试数据加载流程
当一段数据处理逻辑出错时,可以在脚本末尾留一个探针,配合 -i 检查中间状态。
# load_records.py
from __future__ import annotations
def load_records() -> list[dict[str, object]]:
"""模拟从数据源加载记录。"""
return [
{"id": 1, "name": "alpha", "score": 90},
{"id": 2, "name": "beta", "score": None},
{"id": 3, "name": "gamma", "score": 75},
]
def average_score(records: list[dict[str, object]]) -> float:
"""计算平均分,跳过缺失分数的记录。"""
scores = [r["score"] for r in records if r["score"] is not None]
return sum(scores) / len(scores) # type: ignore[arg-type]
records = load_records()
print(average_score(records))$ python -i load_records.py
82.5
>>> records[1] # 探查中间数据
{'id': 2, 'name': 'beta', 'score': None}
>>> [r for r in records if r["score"] is None]
[{'id': 2, 'name': 'beta', 'score': None}]
>>> average_score([{"score": 1}])
1.0用这种方式,不需要反复加 print 再重跑脚本,直接在会话里试验不同输入。
示例三:在 REPL 中验证「表达式结果与 print 结果」的差异
理解 repr 与 str 的区别对写文档示例与测试断言很关键:
>>> value = "tab\there"
>>> value # repr:转义可见
'tab\there'
>>> print(value) # str:转义生效
tab here
>>> items = ["a", "b"]
>>> items # repr 展示结构
['a', 'b']
>>> print(items) # str 展示可读形式
['a', 'b']
>>> from decimal import Decimal
>>> Decimal("1.10") # repr 保留有效位
Decimal('1.10')
>>> print(Decimal("1.10"))
1.10
>>> class Point:
... def __init__(self, x: int, y: int) -> None:
... self.x = x
... self.y = y
... def __repr__(self) -> str:
... return f"Point(x={self.x!r}, y={self.y!r})"
...
>>> Point(1, 2)
Point(x=1, y=2)当自定义类的 __repr__ 设计得当时,在 REPL 中探查对象会非常直观——这也是官方推荐「__repr__ 应尽量返回可重建对象的表达式」的原因。
注意事项
python可能指向 Python 2:在老系统中python命令可能仍是 Python 2,应使用python3或版本管理器明确指定。- REPL 中的代码不会被保存:关掉终端即丢失,重要的逻辑必须及时写入文件。
_只在交互模式自动赋值:脚本里使用_需要自己定义,否则会NameError。- 代码块需以空行结束:输入
for、def、if等复合语句后,要再按一次回车(空行)才会执行。 - 缩进必须一致:REPL 中混用 Tab 与空格会报
IndentationError,建议统一 4 空格。 - 不要用 REPL 跑长时间任务:无法中断后恢复,误关终端会前功尽弃;长任务应写成脚本并支持断点续跑。
exit/quit在脚本中不可用:它们是site模块注入的交互专用对象,脚本中应使用sys.exit()。-c的引号由 Shell 解析:外层引号选择不当会被 Shell 抢先替换变量,注意转义。
最佳实践
- 用
python3或版本管理器明确指定解释器,避免误入 Python 2。 - 把 REPL 当作草稿纸:先在这里试通,再整理成带类型标注的函数保存到文件。
- 给自定义类实现
__repr__,让 REPL 中的探查结果清晰可读。 - 用
python -i script.py加载数据后进入交互,替代反复插print的调试方式。 - 用
dir()和help()就地探索陌生模块,减少切换文档的成本。 - 理解
repr与str的差异,写测试断言时优先关注repr输出的确定性。 - 需要更顺手的交互体验时,升级到 Python 3.13+ 的新 REPL(见下一节)或使用 IPython。
下一节
继续学习:Python 3.13 改进的 REPL