7. 输入和输出

程序的输出可以有多种形式:我们可以将数据以人类可读的形式打印到屏幕上,或者将其写入到文件中以供后续使用。本章将讨论其中的几种实现方式。

7.1. 格式化输出

迄今为止,在 Python 中存在两种输出值的方法:表达式语句以及 print() 函数。(第三种方法是使用文件对象的 write() 方法;标准文件输出可以参考 sys.stdout 方法,其详细内容请查阅库参考手册。)

通常,相较于简单地打印以空格为分隔符的值,你会想要对程序的输出结果进行更多的格式控制。在 Python 中,存在如下几种格式化输出的方法:

  • 使用 f-strings 字符串。这类字符串需要在引号标记之前,以 f 或者 F 作为字符串的开头。你可以使用 {} 包裹想要嵌入到字符串中的 Python 表达式。该表达式可以是某个变量或字面值 。

    >>> year = 2016 ; event = 'Referendum'
    >>> f'Results of the {year} {event}'
    'Results of the 2016 Referendum'
    
  • str.format() 是格式化字符串的第二种方法。相较于第一种方法,该方法需要你进行更多的操作。你仍然可以在字符串中使用 { 和 } 来内嵌变量,也可以进行详细的格式化设计。但这要求你提供与之对应的被格式化的内容。

    >>> yes_votes = 42_572_654 ; no_votes = 43_132_495
    >>> percentage = yes_votes/(yes_votes+no_votes)
    >>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
    ' 42572654 YES votes  49.67%'
    
  • 当然,你也可以通过字符串的切片操作和连接操作来完成字符串的格式化处理。这种方法可以创建任何你想要的格式化形式。在 string 类型中,包含了一些能够将字符串按指定列宽填充的方法。

如果你仅仅想要在调试时打印某些变量,而不进行格式化输出,那么你也可以使用 repr() 函数或者 str() 函数将任意值转化成字符串。

str() 函数能够将值以人类可读的形式呈现,而 repr() 函数则是将值以解释器可读的形式呈现(如果没有与之对应的转换语法,则会产生 SyntaxError 异常)。若某个对象没有适用于人类可读的形式,则 str() 函数的返回值与 repr() 函数相同。在 Python 中,诸如数值、或者是链表、字典这样的结构,上述两种函数各自有统一的呈现方式。但是对于字符串,上述两种函数各自有独特的呈现方式。

如下示例:

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print(s)
The value of x is 32.5, and y is 40000...
>>> # 对于字符串,repr() 函数会添加引号以及反斜杠:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> # repr() 函数的参数也可以是某个 Python 对象:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

string 模块包含 Template 类来提供额外的在字符串中嵌入变量的方法。该方法使用占位符,比如 $x 来内嵌变量,变量的值存放在字典中。这种方法对字符串的格式化控制较少。

7.1.1. 格式化字符串文字

格式化字符串文字 (简称 f-strings )允许你在字符串中包含Python表达式的值,方法是在字符串前面加上 f 或者 F ,并将表达式写成 {expression} 格式。

在表达式后面可以加上格式说明符。这样能够更好地控制表达式值的输出格式。下面的例子将 PI 舍入到小数位数后三位。

>>> import math
>>> print(f'The value of pi is approximately {math.pi:.3f}.')

':' 之后加上一个整数表示该字段的最小字符数,这对于列排序很有用。

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...     print(f'{name:10} ==> {phone:10d}')
...
Sjoerd     ==>       4127
Jack       ==>       4098
Dcab       ==>       7678

也可用其他修饰符来转换要格式化的值。 '!a' 表示应用 ascii()函数 , '!s' 表示应用 str() 函数, 另外 '!r' 表示应用 repr() 函数:

>>> animals = 'eels'
>>> print(f'My hovercraft is full of {animals}.')
My hovercraft is full of eels.
>>> print('My hovercraft is full of {animals !r}.')
My hovercraft is full of 'eels'.

有关这些格式规范的参考,请参阅参考指南 最小字符串格式化

7.1.2. format() 字符串格式化方法

str.format() 的基本使用方法如下:

>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"

其中的括号和字符(称为格式字段)将用传入 str.format() 方法中的对象来替换。括号中的数字可用于表示传递给 str.format() 方法的对象的位置。

>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam

如果在 str.format() 方法中使用关键字参数,其值等于参数名称对应的值。

>>> print('This {food} is {adjective}.'.format(
...       food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.

位置和关键字参数可以任意组合:

>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
                                                       other='Georg'))
The story of Bill, Manfred, and Georg.

如果你有一个不想拆分的长字符,使用名称而不是位置来进行格式化将会是一个更好的方法。这可以简单的使用 '[]' 符号来获取字典中的键

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
...       'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

上面的方法也可以使用 '**' 将字典中的信息进行传递。

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

这与内置函数 vars() 的结合非常有用,这个内置函数能够能够返回包含所有局部变量的字典。

例如,下面的代码生成了将数字本身与其平方、立方整齐对齐的数列:

>>> for x in range(1, 11):
...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

有关字符串格式的完整概述 str.format() ,请参阅 格式字符串语法

7.1.3. 手动格式化字符串

这是一些相同的平方和立方的表,手动格式化:

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # 注意在前一行中使用 'end'
...     print(repr(x*x*x).rjust(4))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

(注意 print() 函数在每一列中添加了一个空格 print() :它经常在参数之间添加空格。)

字符串对象的 str.rjust() 方法在某一栏以给定的宽度会在左边填充空格来对一个字符串进行右对齐。 str.ljust() 和 str.center() 等这些方法相似。 这些方法不写入任何东西,它们只会返回一个新的字符串。如果输入的字符串太长,它们不会截断它,但是会原样返回它。它们会搞乱你的列的排序,但这通常比替代方案更好,它们会通过值来排布。(如果你真的想截断字符串,通常你可以添加一个切片操作,像  x.ljust(n)[:n] 。)

有另外一个方法, str.zfill(), 它会在一个数字型的字符串的左边添加0。它了解正负号:

>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'

7.1.4. 传统字符串格式化方法

操作符 '%' 同样可以被用来格式化字符串。它将该操作符左侧参数解释为 'sprintf()' 样式的字符串应用到操作符右侧参数,并且返回字符串。例如:

>>> import math
>>> print('The value of pi is approximately %5.3f.' % math.pi)
The value of pi is approximately 3.142.

更多信息请参考文档  printf-style String Formatting

7.2. 读写文件

open() 将返回一个文件对象,它使用两个参数作为输入:open(filename,mode),例如:

>>> f = open('workfile', 'w')

第一个参数 filename 是一个包含文件名的字符串。第二个参数 mode 是包含几个单独字母的字符串,用于解释该文件将被用何种方式处理。 mode 参数可以使用 'r' 表示只读模式;'w' 表示只写入模式(同时,同名文件的文件内容将会被抹掉); 'a' 表示在文件末尾追加写入,在该模式下,任何数据都会被自动追加到该文件末尾;'r+' 表示读写操作。 mode 是可选的,未指定的情况下将会默认为 'r' 模式进行读文件。

正常情况下,文件以 text mode 打开, 这就意味着,我们使用字符串数据对文件进行读写操作,这些都是按照某种指定的编码格式进行读写。如果编码格式未被指定,默认编码格式是与平台相关(详情见 open())。 'b' 表示以 二进制模式 打开文件并且追加数据, 数据以字节的形式读写到文件中,但是这种模式应该被用来打开不包含文本的文件中。

text mode 中,默认读操作的行尾  \n 同样是依据指定平台进行选择的。(Unix系统中使用\n , Windows系统中使用 \r\n)。在 text mode 模式下写入文件,默认情况下会将\n转换成对应的平台的行尾字符。这种对文件修改的操作仅适用于文本文件,但是在操作  JPEG 或者  EXE 二进制数据文件过程中,要非常小心的使用二进制模式对他们进行读写操作。

关键字  with 是非常适合用于处理文件对象。它的优势在于,即使发生了 exception,文件操作序列结束后也可以自动关闭。使用关键字 with 处理的代码要远少于 try-finally 块进行处理的代码,例如:

>>> with open('workfile') as f:
...     read_data = f.read()
>>> f.closed
True

如果未使用关键字 with 操作文件,那么必须要调用 f.close() 来结束文件和释放系统资源。未结束一个文件时,Python 的垃圾回收机制将会最终关闭并销毁这个文件对象,但是这个文件将在一段时间内仍然处于打开状态。不同的 Python 实现方式将会在不同的时间对文件进行释放,这将会带来其他的风险。

当文件对象被关闭后,使用 with 或者调用 f.close() 对文件对象的操作都会失效。例如:

>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

7.2.1. 文件对象的方法

该例子的剩下部分将会继续使用上文所建立的文件 f 进行介绍。

读取文件内容的方法为 f.read(size) ,可以读取指定大小的文件内容,可以得到一个字符串(文本模式)或者字节对象(二进制模式)。 size 是一个可选的数值参数,如果 size 省略或为负,可以读取并返回整个文件;要注意的是,文件的大小有可能超出设备内存的上限。否则,最多读取并返回 size 的字节内容。当读取到文件的结尾时,f.read() 会返回一个空字符串(''

>>> f.read()
'This is the entire file.\n'
>>> f.read()
''

f.readline() 可以仅仅从文件里读取一行;读取的字符串中,结尾会有一个换行符(\n),唯一的例外是在最后一行文件不以换行符结束。这个结果是明确的。如果 f.readline() 返回一个空字符串,那么已经读取到文件的结尾,因为空白行只有一个 '\n',一个仅仅包含换行符的字符串。

>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''

要读取多行内容,你可以借助循环遍历整个文档类型。这是一种内存复杂度优先,快速而编码简单的方法:

>>> for line in f:
...     print(line, end='')
...
This is the first line of the file.
Second line of the file

如果你想一行行读取整个文件,你同样可以使用 list(f)f.readlines(),你可以得到一个各行字符串的列表。

f.write(string) 可以写入字符串 string 内容到文件内,返回写入的字符数量。

>>> f.write('This is a test\n')
15

其他类型需要转换才能写入文件中,或者转成字符串(文本模式)或者字节类型(二进制模式):

>>> value = ('the answer', 42)
>>> s = str(value)  # convert the tuple to string
>>> f.write(s)
18

f.tell() 返回一个整数,给出当前在文件中的位置;在二进制模式中,用字节数表示,在文本模式中,用不透明数表示。

要改变当前的位置,可以使用 f.seek(offset, from_what) 。新位置从参考点出发,由参数 offset 值计算得出;参考点位置由 from_what 参数给出。 from_what 为 0 时,从文件开头出发;为 1 时,从当前位置出发;为 2 时从文件结尾出发。 from_what 缺省值为 0 ,采用文件开头作为参考点。

>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)      # Go to the 6th byte in the file
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2)  # Go to the 3rd byte before the end
13
>>> f.read(1)
b'd'

在文本文件(即那些没有使用 b 参数打开的字符串模式文件),只能从文件的开头或是 f.tell() 返回值出发(从文件末尾的定位 seek(0, 2) 为异常)。任何其他的 offset 将产生不可预知的表现。

文件对象有一些附加的方法,比如 isatty()truncate(),它们的使用频率较低;有关文件对象的完整指南,请参阅「参考文档」。

7.2.2. 利用 json结构保存数据

字符串可以轻松地从文件读写。但是数值要花费上更多的功夫,因为 read() 方法仅仅可以返回字符串,这将必须经过一些函数处理,例如 int(),将 '123' 字符串转化为数值 123 。 当你希望保存更复杂的数据类型像嵌套列表或字典时,手动解析和序列化复杂程度难以接受。

Python 提供了被称为 JSON (JavaScript Object Notation) 的流行数据交换格式,而不希望用户不断自行编写和调试代码以储存复杂的数据类型。标准库 json 可以采用 Python 式的数据层级,并且转换成字符串的形式; 这个进程称为 序列化 。反之,从字符串转化成数据类型被称作 反序列化 。得到的字符串对象可以储存在文件或数据中,也可以通过网络连接发送到某个远程主机。

注意

JSON 格式通常被现代应用程序用于数据交换,为许多程序员所熟悉,使其成为交互数据的较优选择。

如果你有一个对象 x ,你能够通过如下的代码访问其 JSON 字符串:

>>> import json
>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'

dumps() 另一个变体函数,叫作 dump() ,只将对象序列化成一个 文本文件 。 所以,如果 f 是一个 文本文件 已打开的可写入文件对象,我们能够利用如下代码:

json.dump(x, f)

要重新解码对象,当 f 是一个 文本文件 已打开的读取用文件对象:

x = json.load(f)

这种简单的序列化技术可以处理列表和字典,但是不足以在 JSON 中序列化任意类的实例。 参考文档 json 包含对此的解释.

也可以看

pickle - Pickle 模块

与 JSON 相反, pickle 是一个允许对任意复杂的 Python 对象进行序列化的协议。因此,它仅仅适配 Python ,不能用于与其他语言编写的应用程序通信。默认情况下,它也是不安全的:如果有人特地设计用于注入攻击的数据,这些不受信任的 Pickle 数据可能会在序列化中被 Python 运行。

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~