Skip to content
This page has been auto-translated and may contain errors.View in English

输出与输入 ​

从你写下第一行代码开始,就会用到这两个工具:print() 在终端里显示值,input() 从用户那里获取文本。它们用起来很简单,但了解它们的行为习惯,能帮你少踩一些一开始就容易碰到的坑。

print() 和 input() 是 Python 标准的终端输入输出函数。两者的可配置程度都比看上去要高。print() 接受一些参数,用来控制多个值之间如何拼接、输出以什么结尾。input() 永远返回字符串,这决定了你要如何处理每一个来自用户的值。

从底层看,print() 写入 sys.stdout,input() 从 sys.stdin 读取(这两个分别是你终端连接的标准输出流和标准输入流)。它们处理的都只是文本,而且输出默认是带缓冲的:Python 会先短暂保留输出内容,然后成批写出,而不是一个字符一个字符地写。这在实际程序中会带来两个后果:输出流和错误流是分开的(stdout 与 stderr 不同),而且当你需要输出立刻显示时,可以强制把缓冲区的内容刷新出去。

Python 如何执行你的代码 ​

Python 会按照你写代码的顺序,从上到下、一行一行地执行,完全按你写的顺序来,不会跳来跳去。你写的顺序就是执行的顺序,永远如此。

python
city = "北京"
print(city)
print("Population: 14 million")

city 先被赋值。然后第一个 print 执行。接着第二个 print 执行。每次都是这个顺序。

这一点很重要,因为你不能在给变量赋值之前就使用它。Python 还没见过这个变量,会报错:

python
print(country)   # NameError: country 还没有被定义
country = "日本"

随着你的程序越写越复杂,要记住:任何你要用的东西,必须先定义好再使用。

Python 采用**顺序执行**的方式:每条语句在遇到时才会被求值,整个文件是从上到下单次遍历执行的,不会提前把整个文件扫描一遍。在变量赋值之前引用它,会在那一行准确地抛出 NameError,而不是提前报错。

python
city = "北京"
print(city)             # 可以正常运行:city 已经绑定了值
print(country)          # NameError:还没有赋值
country = "日本"

规则就是:所依赖的东西,必须在使用它的那一行之前先定义好。

Python 从上到下单次遍历执行你的文件,而且只有在某一行真正运行到的那一刻,才会去查找对应的名字,而不是在读取文件的时候就查找。这就是为什么找不到某个名字时,报的是运行期的 NameError(在这一行真正执行时才报错),而不是你一保存文件就立刻报错。Python 没有变量提升(JavaScript 会悄悄把 var 声明提到作用域顶部的那种机制),也没有前向声明(C 语言里可以先声明一个名字、之后再定义的那种做法)。一个名字从被赋值的那一刻起才存在,在此之前使用它就会报错。

python
print(country)   # 运行期报 NameError,而不是语法错误
country = "日本"
JunoPython 如何执行你的代码 Python 按你写的顺序,从上到下、一行一行地读取文件。几乎所有人都会踩到的一个坑:一个名字必须在使用它的那一行之前就存在,否则会得到 NameError。不会提前跳过去看,也不会先把整个文件读一遍。
JunoPython 如何执行你的代码 从上到下,单次遍历,按顺序执行。一个名字必须在使用它的语句之前完成赋值,否则那一行就会报 NameError。不会提前扫描整个文件。
JunoPython 如何执行你的代码 单次从上到下遍历,名字在每一行运行到时才会被查找。在赋值之前使用一个名字,会在运行期得到 NameError,而不是语法错误。Python 不做变量提升,也没有前向声明,所以顺序真的就是一切。

打印输出 ​

print() 是 Python 向你反馈信息的方式。给它传入任意值,它就会把这个值显示出来,并自动把你给的内容转换成文本。

print() 会通过 str() 把每个参数转换成字符串,用一个分隔符(默认是空格)把它们拼接起来,然后把结果连同一个换行符一起写到标准输出。理解这些默认行为,能让你更容易预测那些可选参数的效果。

print() 完整的签名是 print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)。*objects 这个收集参数可以接受任意数量的位置参数,并对每一个都调用 str()。sep 和 end 控制格式;file 可以把输出重定向到任何可写的流;flush 会强制立即清空缓冲区。

python
print("Hello")    # Hello
print(42)         # 42
print(3.14)       # 3.14
print(True)       # True

一次打印多个值 ​

你可以用逗号分隔多个值,一次性打印出来。Python 默认会在它们之间加一个空格。用 sep 可以改变这个分隔符:

多个位置参数会分别用 str() 转换,然后用 sep 拼接起来。sep 默认是一个空格。覆盖它可以让你不用拼接字符串就能产生格式化的输出:

print() 并不是先拼好一个字符串再写出去,而是依次把每个参数写到流里(对每个参数调用 str()),中间用 sep 分隔,最后用 end 结尾。由于这些写入是一个接一个发生的,而不是作为一个整体的加锁操作,两个同时运行的 print() 调用(在不同线程中)可能会把输出交错在一起。当这一点很重要时,就先拼好一个完整字符串再打印一次。日常使用中,覆盖 sep 仍然比手动拼接字符串更方便:

python
name = "小明"
age = 28
print(name, age)                        # 小明 28
print("Name:", name)                    # Name: 小明
print("2024", "01", "15", sep="-")     # 2024-01-15
print("a", "b", "c", sep=", ")         # a, b, c

控制行末字符 ​

每次调用 print(),默认都会以换行符结尾,所以下一次输出会另起一行。用 end 可以改变这一点。把 end="" 设置为空字符串,可以让下一次打印接着写在同一行:

end 参数会替换默认的换行符。把它设为 "" 可以取消换行,设为 " " 可以在同一行用空格隔开,也可以设为任何其他字符串。把它和 sep 结合起来,你几乎不用手动拼接字符串就能实现大多数输出格式:

sep 和 end 都是仅限关键字参数(你必须按名字传,比如 end="",不能按位置传),默认值分别是一个空格和一个换行符。end="" 是打印半行内容、让下一次调用接着写的标准做法。如果输出需要即时呈现,比如进度提示或实时日志,就加上 flush=True,让 Python 立即写出,而不是等到遇到换行符或者缓冲区被填满。

python
print("Loading", end="")
print("...")
# Loading...

print("one", end=" | ")
print("two", end=" | ")
print("three")
# one | two | three
Juno打印输出print() 会把你给的任何东西转换成文本并显示出来。用逗号分隔多个内容,它们之间会有一个空格,用 sep 可以改变这一点。每次打印结尾都会换行;end="" 能让下一次打印接着写在同一行。
Juno打印输出print() 对每个参数调用 str(),用 sep(默认空格)拼接,再用 end(默认换行符)收尾。改写其中任意一个,就能格式化输出而不用手动拼接字符串。
Juno打印输出print() 把每个参数经由 str() 写出,中间用 sep,末尾用 end,两者都是仅限关键字参数。想接着写在同一行,用 end="";想让输出立刻显示,用 flush=True。

用 f-string 格式化输出 ​

构建消息最简洁的方式是 f-string。在起始引号前加上 f,然后把任意变量或表达式包在花括号里,Python 会在运行时把它填进去。{} 里可以放任意值、计算式,甚至方法调用。

f-string 会在运行时对 {} 里的任意表达式求值,并把结果作为字符串嵌入进去。值后面的冒号会引出一个格式说明符:一种简洁的语法,用来控制小数位数、对齐方式和数字格式。它们比字符串拼接更快、更易读,而且不需要显式调用 str()。

f-string 里的每个 {} 最终都会调用 format(value, spec),而这个调用会转交给该值自身的 __format__ 方法(一个双下方法,也就是 Python 在格式化一个值时会调用的双下划线方法)。所以任何类都可以通过定义 __format__ 来决定自己在 f-string 里的呈现方式。转换标志 !r、!s 和 !a 会先对值分别调用 repr()、str() 或 ascii();!r 是调试时最值得记住的一个,因为它会按你在代码里输入这个值的方式来展示它(包括引号、转义符等),下面就能看到实际效果。

python
name = "小明"
score = 980

# 字符串拼接:写起来笨拙,数字还要手动 str() 转换
print("Player: " + name + ", Score: " + str(score))

# f-string:可读性好,不需要手动转换
print(f"Player: {name}, Score: {score}")

{} 里可以放任意表达式:算术运算、方法调用(比如 .upper(),会在 字符串 一章完整讲到)、格式说明符:

python
price = 49.99
tax = 0.2
total = price * (1 + tax)

print(f"Total: {total:.2f}")          # Total: 59.99
print(f"Name: {name.upper()}")        # Name: 小明(大写形式无变化)
print(f"2 + 2 = {2 + 2}")             # 2 + 2 = 4

: 后面的格式说明符控制值的显示方式:

python
ratio = 0.8765
count = 1234567
label = "revenue"

print(f"{ratio:.1%}")       # 87.6%
print(f"{count:,}")         # 1,234,567
print(f"{label:>12}")       # "     revenue"

:.2f 表示"保留两位小数"。处理价格和度量值时你会经常用到它。其他的格式说明符则是在你需要的时候用。最重要的一点是:{} 里可以放任何东西,不只是变量名。

:.2f 和 :.0% 能满足大部分格式化需求。对齐说明符(>、<、^)配合宽度值,可以生成整齐的表格式输出。一般模式是 {value:[align][width][.precision][type]}。一旦认清各部分的含义,任何格式说明符都能看懂,不需要死记硬背所有组合。

同样的格式说明符,对不同的类型可能意味着不同的效果,因为每种类型都是自己决定如何格式化自己的。f"{'hi':5}" 会在文本右侧补齐(文本默认左对齐),而 f"{42:5}" 会在数字左侧补齐(数字默认右对齐):同样是 :5,结果却相反。每个内置类型都为这套格式化的迷你语言定义了自己的规则,自定义类也可以通过定义 __format__ 来设定自己的规则。

Juno用 f-string 格式化输出 在起始引号前加上 f,然后把任何东西包进 {} 里:变量、算式,甚至方法调用都行。加个冒号可以加格式设置,比如 :.2f 表示保留两位小数。比用 + 拼字符串清爽多了。
Juno用 f-string 格式化输出f"..." 会把任意表达式的结果直接嵌入字符串,不需要 str()。冒号后面就是格式说明符::.2f、:,、:>12 之类。比字符串拼接更清晰也更快。
Juno用 f-string 格式化输出 每个 {} 调用的都是这个值自身的 __format__,所以冒号后面的格式说明符具体是什么含义,由那个类型自己决定。!r 是最值得记住的一个:它会按你在代码里输入的方式来展示这个值,这样调试时多余的空白字符之类看不见的东西就会暴露出来。

获取用户输入 ​

input() 会暂停你的程序,等待用户输入内容。用户输入的内容(并按下回车后)会作为返回值传回来。括号里的字符串是显示给用户看的提示信息。

python
name = input("What's your name? ")
print(f"Hello, {name}!")

无论用户输入什么,input() 永远返回一个字符串。输入 42,你得到的是 "42",而不是数字 42。要对它做算术运算,需要显式转换:

python
age = int(input("How old are you? "))
print(f"In ten years you'll be {age + 10}.")

如果用户输入了无法转换的内容会怎样?Python 会抛出 ValueError。妥善处理这种情况的方法,会在 文件与异常 一章讲到。

input() 会把提示信息写到标准输出,从标准输入读取一行内容,去掉末尾的换行符,然后把结果作为字符串返回。这里没有类型推断。从终端来的一切内容都是文本形式;你需要在数据进入程序的边界处,显式转换成你需要的类型。

python
name = input("What's your name? ")
age = int(input("How old are you? "))

这个模式(接收文本,转换成你需要的类型)适用于任何外部数据进入程序的场景。int()、float() 和 str() 是常用的转换工具。如果字符串无法转换,Python 会抛出 ValueError,这会在 文件与异常 一章讲到。

input() 其实是一个很薄的封装:它把提示信息写到 sys.stdout(标准输出),并刷新缓冲区,确保提示信息在程序阻塞等待输入之前就显示出来,然后从 sys.stdin(标准输入)读取一行,去掉末尾的换行符,最后返回一个 str。它在设计上永远返回 str,因为 Python 没法猜出 "42" 到底是想表示整数、浮点数,还是纯字面文本。所以你需要在数据的边界处自己做转换。这也是 Python 输入输出最基本的模式:外部数据都以文本形式到达,由你的代码在入口处决定它的类型。

python
name = input("Enter your name: ")
score = float(input("Enter your score (0.0 to 1.0): "))

print(f"Name:  {name!r}")      # !r 会暴露任何看不见的空白字符
print(f"Score: {score:.1%}")
Juno获取用户输入input() 会显示你的提示信息,等待一行输入,然后永远把它作为 string 返回,即使用户输入的是数字。需要数字?在拿到输入的时候,自己用 int() 或 float() 转换一下,然后就能用正确的类型继续往下写了。
Juno获取用户输入input() 永远返回 str,没有类型推断。在数据边界处用 int() 或 float() 转换,如果文本无法解析,就会得到 ValueError。这种"先接收再转换"的模式,在处理外部数据的地方到处都能见到。
Juno获取用户输入 从 input() 拿到的一切,在设计上都是 str,因为 Python 没法猜出你的本意。要在数据的入口处做转换,并处理解析失败时的 ValueError。这和其他所有外部数据进入程序的地方,模式都是一样的。

写入 stderr ​

print() 默认写入标准输出:也就是会在终端显示、并能流入管道的那个流。Python 还有标准错误,这是一个专门用于诊断信息和警告的独立流。它们在终端里看起来一模一样,但其实是两个不同的流:当你把一个脚本的输出通过管道传给另一个命令时,只有 stdout 会被传过去。stderr 则总是直接到达终端。

写入 stderr 要用到 print() 的 file 参数。这需要先导入 sys,这部分内容会在 模块 一章讲到。现在,你只需要知道这两个流的存在,以及它们为什么要分开就够了。

实践一下 ​

一个能根据用户输入个性化内容的小测验:

python
name = input("What's your name? ")
subject = input("Which subject? ")

print(f"Okay, {name}. Starting your {subject} quiz.")
print("Good luck!")

两个输入都以字符串形式返回,直接放进 f-string 里使用。因为这里是把它们当作文本使用,而不是数字,所以不需要做任何转换。

一个带对齐表格输出的温度转换器:

python
celsius = float(input("Temperature in Celsius: "))
fahrenheit = celsius * 9 / 5 + 32

print(f"{'Celsius':>12} {'Fahrenheit':>12}")
print(f"{celsius:>12.1f} {fahrenheit:>12.1f}")

float() 既能处理用户输入的整数,也能处理小数。无论数字有多少位,>12 这个格式说明符都能保持列的对齐。可以试着输入 100 和 -40:不管哪种情况,输出都会保持整齐。

用 !r 精确显示 Python 实际收到的内容,这在输出看起来不对劲时很有用:

python
name = input("Enter your name: ")
score = float(input("Enter your score (0.0 to 1.0): "))

print(f"Name:   {name!r}")
print(f"Score:  {score!r}")
print()
print(f"Result: {name}: {score:.1%}")

当输入干净整洁时,{name} 和 {name!r} 显示的内容是一样的。差别会在有多余的空格或者其他看不见的字符时显现出来。养成调试时用 !r 的习惯,能让异常的值立刻显形。