Skip to content

基本输入输出

知识点回顾

警告

此处仅对一些易忽视的、容易出错的知识点和常用的知识点进行回顾,不能代替课本和课件的学习!同时,我们在这里会提及的东西可能会超出标题所框定的范围,但尽可能限制在课程范围之内,如果有超出范围的内容,将放在蓝色方框中以示提醒.

基本的输入和输出函数

读者需要熟悉的函数是:

  • input() 函数
  • print() 函数

需要注意的是,print() 函数可以传入一系列参数,这一系列参数在输出时,中间会多出一个空格. 因此,一种做法是将其全部转化成字符串类型,中间用 + 连接,另一种做法是使用格式化字符串进行输出,如下一节所述.

格式化字符串

首先,需要说明的是,格式化字符串是一个相当复杂的主题. 在详细介绍这个主题之前,我们或许需要先框定一些术语. 称一个需要被处理的字符串为格式化字符串(format string),其中不需要被替换,可以原样保留在格式化后的文本中的内容成为字面量(literals),而需要被替换的部分称作替换字段(replacement fields).

接下来,请就程序语言设计者的角度思考,如何区分格式化字符串中的字面量和替换字段?这一定是一个需要处理的问题,因为替换字段就是格式化字符串的核心. 因此,我们引入 {} 来标记替换字段. 在 {} 中的东西会在格式化的过程中被替换为用于格式化此字符串的变量,例如:

"{} is a kind of {}".format("python", "snake")
# python is a kind of snake

第二个问题是:当我们有多个替换字段的时候,如何区分它们?最简单的方法就是予以编号——当然,按照程序员的惯例,从零开始,例如:

"{0} is a kind of {1}".format("ruby", "germ")
# ruby is a kind of germ

因为我们传递用于格式化的变量的时候,它是有顺序的,而格式化字符串内部的顺序可以与原来的顺序不同,甚至可以重复(实际上,这才是标号最重要的功能!). 比方说:

"{0} is a kind of {1} but {1} is not a kind of {0}".format("window", "furniture")
# window is a kind of furniture but furniture is not a kind of window

另外,你甚至可以命名格式化字符串:

"{name} is a great {job}".format(name="haskell", job="computer scientist")
# haskell is a great computer scientist

在 Python 3.6 引入了 f-字符串之后,这变得更加有用了:

name = "haskell"
job = "computer scientist"
f"{name} is a great {job}"
# haskell is a great computer scientist

f-字符串相当于省略了原来的替换字段,并且以里面的标识符(identifier,比如变量名)的内容直接作为被格式化的对象.

接下来,我们想问的是,输出已经可以了,那么应该怎么控制输出长什么样?在此,我们只讨论几种最常见的情形. 通常,我们用 : 标识后面是对这个输出的标记,: 前面则会放入标号或者标识符.

精度控制

最常见的用法是控制小数的输出. 通常,我们会写成以下形式:

>>> '{:.3f}'.format(123.456)
'123.456'
>>> '{:.3f}'.format(123.0)
'123.000'

注意,这里的 f 标识是必须的,没有它的话,系统会自动以 g 标识输出,这超出了这门课的讨论范围,有兴趣的可以参见参数类型一节.

宽度控制

读者对于输出宽度这个概念应该并不陌生. 对于一个字符串,我们希望它能扩展到一个定长;对于一个数字,我们希望它能被某些值填上空缺. 那么首先我们要说明的是对齐:

属性 含义
< 强制字段在可用空间内左对齐(这是大多数对象的默认值).
> 强制字段在可用空间内右对齐(这是数字的默认值).

然后,需要指定对象的宽度,也就是说,需要对齐到多长. 这通过给定一个数字来实现. 下面的例子会给出一个清晰的呈现:

>>> '{:<30}'.format(1234)
'1234                          '
>>> '{:>30}'.format(1234) # 数字会默认为右对齐,所以这里可以不用加 > 符号
'                          1234'

另外的(可能事关千万的)问题是,用什么东西来填充空白的部分?默认地,如果不指明就会使用空格,但是,也有一些比较神奇的例子,比如说,如果你希望让数字前后有 0,那么

>>> '{:0>30}'.format(1234)
'000000000000000000000000001234'
>>> '{:0<30}'.format(1234)
'123400000000000000000000000000'

其中的 0 可以被替换成大部分字符,例如:

>>> '{:a<30}'.format(1234)
'1234aaaaaaaaaaaaaaaaaaaaaaaaaa'
>>> '{:*<30}'.format(1234)
'1234**************************'

参数类型

提示

这一节不是重点. 可以参见 Python 官方文档中的表格进行测试和理解.

我们常用的参数类型有两类:面向整数的和面向小数的. 面向小数的 f 无需赘述,下面对面向整数的几个常用的类型标识做一个讨论.

请看下面的格式化字符串形式:

>>> "int: {0:d};  hex: {0:x};  oct: {0:o};  bin: {0:b}".format(42)
'int: 42;  hex: 2a;  oct: 52;  bin: 101010'
>>> "int: {0:d};  hex: {0:#x};  oct: {0:#o};  bin: {0:#b}".format(42)
'int: 42;  hex: 0x2a;  oct: 0o52;  bin: 0b101010'

显而易见,d 是十进制,x 为十六进制,o 为八进制而 b 为二进制. 对于非十进制的情形,# 表示是否需要加表明身份的前缀.

一些奇妙的补充

拓展内容

这里是一些好玩的奇怪实现,基本上不会碰到,也不应做要求. 但笔者觉得或许有点意思,有兴趣的读者可以对照官方文档尝试执行并解释其输出:

>>> '{2}, {1}, {0}'.format(*'abc')
'c, b, a'
>>> "{0} is represented by {0!r} internally".format("abc")
"abc is represented by 'abc' internally"
>>> "{:{fill}{fill}30}".format("center", fill="^")
'^^^^^^^^^^^^center^^^^^^^^^^^^'

常见问题

Q: 如何输入多个处在同一行中的东西?

回顾我们所学,input() 函数会读取一整行的输入,作为字符串类型返回. 因此,输入两个在同一行中的对象的正确方法是使用字符串方法. 特别地,我们会用到 split() 方法来将输入分成多个部分,然后保存成一个列表,再将列表进行拆解. 例如,如果输入两个以逗号分隔的元素,例如 11, 13,我们应该写出这样的代码:

a, b = input().split(", ")

然后再对对应的字符串进行处理得到结果. 需要提醒的是,如果面对可能有多个空格分隔的元素,例如 11 12 13 14,需要使用 split() 方法的默认形态,即:

l = input().split() # 请思考,此时的 l 是什么类型的?

关于此行为,在 Python 官方文档中是这样描述的:

如果 sep 未指定或为 None,则会应用另一种拆分算法:连续的空格会被视为单个分隔符,其结果将不包含开头或末尾的空字符串,如果字符串包含前缀或后缀空格的话. 因此,使用 None 拆分空字符串或仅包含空格的字符串将返回 [].

其中 sep 就是传入 split() 方法的参数. 关于更详细的内容,可以参考类型与值一节.

Q: 为什么 round() 函数调用完之后输出的结果会少末尾 0?

注意,round() 函数是将一个浮点数根据给定的精度转化成另一个浮点数,而浮点数输出时则会取末尾 0 尽可能少的形式. 因此,如果需要保证末尾 0 的个数,请采用格式化字符串的方法进行输出.

Q: round() 的规则是什么?

这里有一些非常容易混淆的地方. 在大部分情况下,round() 可能并不会按照我们预期的四舍五入的方式进行:

>>> round(0.5)
0
>>> round(1.5)
2

这里的原因是,round() 函数在处理的时候,是向最近的偶数进行舍入的.

而对于下面的例子则又完全不同:

>>> round(2.45, 1)
2.5
>>> round(2.55, 1)
2.5
>>> round(2.65, 1)
2.6
>>> round(2.75, 1)
2.8

这里的原因是浮点数的精度问题. 关于详细的解释,请阅读 Python 官方文档的浮点算术:争议和限制部分.

关于浮点数误差

一个极其经典的例子是:

>>> 0.1 + 0.2 == 0.3
False
>>> 0.1 + 0.2
0.30000000000000004

简单的理解方式是,例如我们可以输出这些数保留到小数点后 60 位的结果:

>>> '{:.60f}'.format(2.45)
'2.450000000000000177635683940025046467781066894531250000000000'
>>> '{:.60f}'.format(2.55)
'2.549999999999999822364316059974953532218933105468750000000000'
>>> '{:.60f}'.format(2.65)
'2.649999999999999911182158029987476766109466552734375000000000'
>>> '{:.60f}'.format(2.75)
'2.750000000000000000000000000000000000000000000000000000000000'

在这里只有 2.75 是可以被精确表示的,而其他三个数则是无法精确表示的. round() 函数得到这样的结果,实际上就是因为这个原因.

Comments