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

输出和输入

你从写下的第一行代码就会用到两个工具:print() 在终端中显示数值,input() 从用户那里获取文本。它们很简单,但了解它们的工作方式能让你避免一些早期的意外。

print()input() 是 Python 的标准终端 I/O 函数。两者的可配置性都比初看起来要高。print() 接受控制如何连接数值以及输出如何结尾的参数。input() 总是返回字符串,这决定了你如何处理来自用户的每个数值。

在底层,print() 写入 sys.stdoutinput() 读取 sys.stdin(即你的终端连接的标准输出和标准输入流)。两者都仅处理文本,输出默认是缓冲的:Python 会暂时保存输出,然后分块写入,而不是一次一个字符。这在真实程序中有两个重要的后果:输出和错误流向不同的目标(stdout 对比 stderr),而且当你需要输出立即出现时,你可以强制刷新缓冲区。

Python 如何运行你的代码

Python 从上到下,一次一行,按照你写的顺序执行你的代码。没有跳跃。你写东西的顺序就是它们运行的顺序。总是这样。

python
city = "北京"
print(city)
print("人口:2,171 万")

首先给 city 赋值。然后第一个 print 运行。然后第二个 print 运行。每次都是这样的顺序。

这很重要,因为你不能在赋值前使用一个变量。Python 还没有见过它,会抛出一个错误:

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

当你的程序变得更复杂时要记住这一点:你使用的任何东西都必须在使用前定义。

Python 使用**顺序执行**:每条语句在遇到时都会被求值,整个文件从上到下只有一次遍历。没有提前扫描整个文件。在赋值前引用一个名称会在那一行抛出 NameError,不会更早。

python
city = "北京"
print(city)             # 可行:city 已经被绑定了
print(country)          # NameError:还没有被赋值
country = "中国"

规则:依赖项必须在使用它们的那一行前定义。

Python 从上到下用一次遍历运行你的文件,它只在那一行实际运行时查找一个名称,而不是在第一次读取文件时。这就是为什么缺失的名称是 NameError(在运行时,当那一行执行时)而不是你保存时立刻抛出的错误。没有提升(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()sepend 控制格式;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)                    # 名字: 小明
print("2024", "01", "15", sep="-")     # 2024-01-15
print("a", "b", "c", sep=", ")         # a, b, c

控制行尾

每个 print() 调用默认以换行符结尾,所以下一个输出从新的一行开始。用 end 改变这一点。设置 end="" 让下一个 print 继续在同一行:

end 参数替代默认换行符。设置它为 "" 来抑制换行符,为 " " 来保持在同一行并加一个空格,或任何其他字符串。结合 sep,你可以产生大多数输出格式而不需要手工构建字符串:

sepend 是仅关键字的(你通过名称传它们,像 end="",从不按位置),默认为空格和换行符。end="" 是打印部分行并让下一个调用继续的标准方式。对于需要在发生时出现的输出,比如进度指示器或实时日志,加上 flush=True 这样 Python 会立刻写入而不是等待换行符或缓冲区填满。

python
print("加载中", end="")
print("...")
# 加载中...

print("一", end=" | ")
print("二", end=" | ")
print("三")
# 一 | 二 | 三
Juno打印输出print() 把你给它的任何东西变成文本并显示出来。给它传几个用逗号分隔的东西,你会在它们之间得到一个空格,用 sep 改变这个。每个 print 在最后会换到新的一行;end="" 让下一个保持在同一行。
Juno打印输出print() 对每个参数运行 str(),用 sep(默认是空格)连接它们,然后用 end(默认是换行符)结尾。覆盖其中任何一个来格式化输出而不需要拼接字符串。
Juno打印输出print() 通过 str() 写每个参数,中间是 sep 最后是 end,两个都是仅关键字的。用 end="" 继续一行,当输出需要立刻出现时用 flush=True

用 f-strings 格式化输出

构建消息最干净的方式是 f-strings。在开引号前放 f,然后在大括号中包裹任何变量或表达式。Python 在运行时填入它。你可以在 {} 里放任何值、计算或方法调用。

f-strings 在运行时求值 {} 中的任何表达式并把结果嵌入为字符串。冒号后面的东西引入一个格式规范:一个紧凑的语法来控制小数位数、对齐和数字格式。它们比连接更快更可读,也不需要显式的 str() 调用。

f-string 中的每个 {} 最终会调用 format(value, spec),这会传递给值自己的 __format__ 方法(一个双下划线方法,Python 调用它来格式化一个值)。所以任何类都可以通过定义 __format__ 来决定它在 f-string 中的样子。转换标志 !r!s!a 分别在值上运行 repr()str()ascii()!r 是要记住的,用于调试,因为它显示一个值的写法方式(包括引号、转义和所有),下面有实际示例。

python
name = "小明"
score = 980

# 连接:笨重,对数字需要 str()
print("玩家:" + name + ",分数:" + str(score))

# f-string:可读,无需手工转换
print(f"玩家:{name},分数:{score}")

你可以在 {} 里放任何表达式:算术、方法调用(比如 .upper(),在字符串章节全面覆盖)、格式规范:

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

print(f"总额:{total:.2f}")          # 总额:59.99
print(f"名字:{name.upper()}")        # 名字:小明
print(f"2 + 2 = {2 + 2}")             # 2 + 2 = 4

冒号后的格式规范控制值如何显示:

python
ratio = 0.8765
count = 1234567
label = "收入"

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

:.2f 意思是"两位小数"。你会不断用到它来处理价格和测量值。其他的一切在你需要时就在那里。主要的事:任何东西都可以在 {} 里,不只是变量名。

:.2f:.0% 覆盖了大多数格式化需求。对齐符(><^)加上宽度会产生整洁的表格输出。通用模式是 {value:[align][width][.precision][type]}。一旦你识别了各个部分,任何规范都能读懂而不需要记住所有的组合。

同一个规范可能对不同的类型有不同的意思,因为每个类型格式化自己。f"{'hi':5}" 在右边填充文本(文本默认左对齐)而 f"{42:5}" 在左边填充(数字默认右对齐):相同的 :5,相反的结果。每个内置类型都在这个格式小语言中有自己的规则,自定义类通过定义 __format__ 来设置自己的。

Juno用 f-strings 格式化输出 在开引号前放 f 并在 {} 中包裹任何东西:一个变量、一个和,甚至一个方法调用。冒号加上格式化,比如 :.2f 表示两位小数。比用 + 拼接字符串干净得多。
Juno用 f-strings 格式化输出f"..." 把任何表达式的结果直接放进字符串,不需要 str()。冒号后来格式规范::.2f:,:>12 等。比连接更干净更快。
Juno用 f-strings 格式化输出 每个 {} 调用值自己的 __format__,所以冒号后的规范意思由那个类型决定。!r 是要记住的:它显示一个值的你会如何输入的方式,这让你在调试时尾部空格和其他看不见的东西立刻显现出来。

从用户获取输入

input() 暂停你的程序并等待用户输入一些东西。无论他们输入什么(并在其上按 Enter),都会作为返回值回来。括号中的字符串是用户看到的提示。

python
name = input("你叫什么名字?")
print(f"你好,{name}!")

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

python
age = int(input("你今年多少岁?"))
print(f"十年后你会是 {age + 10} 岁。")

如果用户输入不能被转换的东西怎么办?Python 抛出 ValueError。适当的处理方式在文件和异常章节中覆盖。

input() 把提示写到标准输出,从标准输入读一行,去掉末尾换行符,并以字符串形式返回结果。没有类型推断。来自终端的一切都作为文本到达;你通过在边界显式转换来声明你需要什么类型。

python
name = input("你叫什么名字?")
age = int(input("你今年多少岁?"))

这个模式(接收文本,转换为你需要的类型)适用于外部数据到达的每个地方。int()float()str() 是转换工具。如果字符串不能被转换,Python 抛出 ValueError,在文件和异常章节中覆盖。

input() 是一个薄包装:它把提示写到 sys.stdout(标准输出),刷新它以让提示在程序阻塞前出现,从 sys.stdin(标准输入)读一行,去掉末尾换行符,然后返回一个 str。它总是一个 str,通过设计,因为 Python 无法猜测 "42" 是否意味着整数、浮点数或字面文本。所以你在边界转换。这是 Python I/O 的基本形状:外部数据作为文本到达,你的代码在入口点决定它的类型。

python
name = input("输入你的名字:")
score = float(input("输入你的分数(0.0 到 1.0):"))

print(f"名字:{name!r}")      # !r 显示任何看不见的空格
print(f"分数:{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("你叫什么名字?")
subject = input("哪个科目?")

print(f"好的,{name}。开始你的 {subject} 测验。")
print("祝你好运!")

两个输入都作为字符串回来并直接进入 f-strings。不需要转换因为你是把它们作为文本使用,不是数字。

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

python
celsius = float(input("摄氏度温度:"))
fahrenheit = celsius * 9 / 5 + 32

print(f"{'摄氏度':>12} {'华氏度':>12}")
print(f"{celsius:>12.1f} {fahrenheit:>12.1f}")

float() 处理来自用户的整数和小数。>12 规范保持列对齐不管数字有多少位。试试输入 100-40:输出要么是整洁的。

使用 !r 来显示 Python 接收到的正是什么,当输出看起来错误时很有用:

python
name = input("输入你的名字:")
score = float(input("输入你的分数(0.0 到 1.0):"))

print(f"名字:   {name!r}")
print(f"分数:  {score!r}")
print()
print(f"结果:{name}{score:.1%}")

当输入干净时 {name}{name!r} 显示相同。当有尾部空格或其他看不见的字符时不同会出现。在调试时养成 !r 的习惯让意外的值立刻变得可见。