第 5 章:输入和输出

如果程序没有从程序用户那里获取任何输入,那么它就毫无意义。同样,如果程序没有输出,那么人们就会问我们为什么要有这个程序。输入和输出操作可以定义任何程序的用户体验和可用性。本章将介绍如何将信息或数据输入程序,以及如何显示或保存到文件。本章不讨论与数据库的交互,而是讨论更基本的与文件的交互。在本章中,您将学习如何通过终端或命令行环境为程序输入数据,以及如何从文件读取输入并写入文件。阅读完本章后,您应该了解如何使用 pickle 模块将 Python 对象持久化到磁盘,以及如何从磁盘检索对象并使用它们。

从键盘输入

如上所述,几乎每个程序都以某种形式从用户那里获取输入。大多数基本应用程序允许通过终端或命令行环境进行键盘输入。Python 使键盘输入变得容易,并且与 Python 中的许多其他技术一样,有多种方法可以实现键盘输入。在本节中,我们将介绍执行此任务的每种不同方法,以及一些用例。最后,您应该能够识别最适合您需求的输入和输出执行方法。

sys.stdinraw_input

使用 std.stdin 是迄今为止从命令行或终端读取输入的最广泛使用的方法。此过程包括导入 sys 包,然后编写一条消息提示用户输入一些内容,最后通过调用 sys.stdin.readln() 读取输入并将返回值分配给一个变量。该过程类似于清单 5-1 中显示的代码。

清单 5-1. 使用 ``sys.stdin``

# Obtain a value from the command line and store it into a variable
>>> import sys
>>> fav_team = sys.stdin.readline()
Cubs
>>> sys.stdout.write("My favorite team is: %s" % fav_team)
My favorite team is: Cubs

您可以看到 sys 模块非常易于使用。然而,更简单的则是 raw_input 函数。它基本上会在命令行或终端上生成一些文本,接受用户输入,并将输入分配给一个变量。让我们看看上面使用 raw_input 语法的相同示例。

清单 5-2. 使用 ``raw_input``

# Obtain a value using raw_input and store it into a variable
>>> fav_team = raw_input("Enter your favorite team: ")
Enter your favorite team: Cubs

请注意,另一个函数 input 执行类似的任务。但是,input 函数需要谨慎使用,因为它可能存在潜在的安全风险。 raw_input 函数只返回输入的文本,而 input 函数会接受输入并将其评估为表达式,从而打开任意代码执行的大门。最安全的方法是避免使用 input

从 Jython 环境获取变量

可以从 Jython 环境直接检索值以供应用程序使用。例如,我们可以获取系统环境变量或在运行程序时传递到命令行或终端的字符串。

要在 Jython 应用程序中使用环境变量值,只需导入 os 模块并使用其 environ 字典来访问它们。由于这是一个字典对象,您可以通过简单地键入 os.environ 来获取所有环境变量的列表。

清单 5-3. 获取和修改系统环境变量

>>> import os
>>> os.environ["HOME"]
'/Users/juneau'
# Change home directory for the Python session
>>> os.environ["HOME"] = "/newhome"
>>> os.environ["HOME"]
/newhome'

当您从命令提示符或终端执行 Jython 模块时,您可以使用 sys.argv 列表,该列表从调用 Jython 模块后命令提示符或终端获取值。例如,如果我们希望程序用户输入一些要由模块使用的参数,他们只需调用模块,然后键入所有文本条目,并在条目之间使用空格,并使用引号传递包含空格的参数。参数的数量可以是任意大小(受 shell 限制),因此可能性是无限的。

清单 5-4. 使用 ``sys.argv``

# sysargv_print.py – Prints all of the arguments provided at the command line
import sys
for sysargs in sys.argv:
    print sysargs

用法

$ jython sysargv_print.py test test2 "test three"
sysargv_print.py
test
test2
test three

如您所见,sys.argv 中的第一个条目是脚本名称,然后在模块名称之后提供的每个附加参数都会添加到 sys.argv 列表中。这对于创建用于自动化任务等的脚本非常有用。

文件 I/O

您在第 2 章中了解了一些关于 file 数据类型的知识。在那一章中,我们简要讨论了使用这种类型可以执行的一些操作。在本节中,我们将详细介绍可以使用 file 对象执行的操作。我们将从基础知识开始,然后深入探讨。首先,您应该查看表 5-1,其中列出了 file 对象可用的所有方法及其功能。

表 5-1. ``file`` 对象方法

方法 描述
close() 关闭文件
fileno() 返回整数文件描述符
flush() 用于刷新或清除输出缓冲区并将内容写入文件
isatty() 如果文件是交互式终端,则返回 1
next() 这允许对文件进行迭代。返回文件中的下一行。如果未找到行,则引发 StopIteration。
read(x) 读取 x 个字节
readline(x) 读取单行,最多 x 个字符,或者如果省略 x,则读取整行。
readlines(size) 将文件中的所有行读入列表。如果 size > 0,则读取该数量的字符。
seek() 将光标移动到文件中的新位置
tell() 返回光标的当前位置
truncate(size) 截断文件大小。除非指定,否则大小默认为当前位置。
write(string) 将字符串写入文件对象。
writelines(seq) 将序列中包含的所有字符串写入文件,不带分隔符。

我们将从创建一个用于使用的文件开始。正如第 2 章中所讨论的,open(filename[, mode]) 内置函数以特定方式创建并打开指定文件。 mode 指定我们将以何种模式打开文件,无论是读取、读写等等。

清单 5-5. 创建、打开和写入文件

>>> my_file = open('mynewfile.txt','w')
>>> first_string = "This is the first line of text."
>>> my_file.write(first_string)
>>> my_file.close()

在这个例子中,文件“mynewfile.txt”在调用 open 函数之前不存在。如果它已经存在,则旧版本将被新版本覆盖,并且现在为空。该文件是在写入模式下创建的,然后我们就这样做了,将字符串写入文件。现在,重要的是要提到 first_string 实际上直到关闭或执行 flush() 才会写入文件。还值得一提的是,如果我们要关闭文件,重新打开它,并在文件上执行后续的 write() 操作,那么文件中的先前内容将被新写入的内容覆盖。

现在我们将逐步介绍每个文件函数的示例。这个例子的主要目的是为您提供一个查找实际工作文件 I/O 代码的地方。

清单 5-6.

# Write lines to file, flush, and close
>>> my_file = open('mynewfile.txt','w')
>>> my_file.write('This is the first line of text.\n')
>>> my_file.write('This is the second line of text.\n')
>>> my_file.write('This is the last line of text.\n')
>>> my_file.flush()  # Unneccesary if closing the file but useful to clear buffer
>>> my_file.close()
# Open file in read mode
>>> my_file = open('mynewfile.txt','r')
>>> my_file.read()
'This is the first line of text.\nThis is the second line of text.\nThis is the last line of text.\n'
# If we read again, we get a '' because cursor is at the end of text
>>> my_file.read()
''
# Seek back to the beginning of file and perform read again
>>> my_file.seek(0)
>>> my_file.read()
'This is the first line of text.This is the second line of text.This is the last line of text.'
# Seek back to beginning of file and perform readline()
>>> my_file.seek(0)
>>> my_file.readline()
'This is the first line of text.\n'
>>> my_file.readline()
'This is the second line of text.\n'
>>> my_file.readline()
'This is the last line of text.\n'
>>> my_file.readline()
''
# Use tell() to display current cursor position
>>> my_file.tell()
93L
>>> my_file.seek(0)
>>> my_file.tell()
0L
# Loop through lines of file
>>> for line in my_file:
...     print line
...
This is the first line of text.
This is the second line of text.
This is the last line of text.

有一些只读属性,我们可以使用它们来获取有关文件对象的更多信息。例如,如果我们正在处理一个文件,并且想要查看它是否仍然打开或是否已关闭,我们可以查看文件上的 closed 属性以返回一个布尔值,指示文件是否已关闭。表 5-2 列出了这些属性中的每一个以及它们告诉我们有关 file 对象的信息。

表 5-2. 文件属性

属性 描述
closed 返回一个布尔值,指示文件是否已关闭。
encoding 返回一个字符串,指示文件上的编码。
mode 返回文件的 I/O 模式(例如,“r”、“w”、“r+”、“rb”等)。
name 返回文件名。
newlines 返回文件中的换行符表示。这会跟踪在读取文件时遇到的换行符类型。允许通用换行符支持。

清单 5-7. 文件属性用法

>>> my_file.closed
False
>>> my_file.mode
'r'
>>> my_file.name
'mynewfile.txt'

Pickle

Python 语言中最流行的模块之一是 pickle 模块。该模块的目标基本上是允许将 Python 对象序列化并持久化到磁盘上的文件格式中。可以使用此模块将腌制后的对象写入磁盘,也可以将其读回并以对象格式使用。几乎所有 Python 对象都可以使用 pickle 持久化。

要将对象写入磁盘,我们调用 pickle() 函数。该对象将以可能无法被其他任何东西使用的格式写入文件,但我们可以将该文件读回我们的程序并使用该对象,就像它在写入之前一样。在下面的示例中,我们将创建一个 Player 对象,然后使用 pickle 将其持久化到文件。稍后,我们将将其读回程序并使用它。在使用 pickle 模块时,我们将使用 file 对象。

清单 5-8. 使用 Pickle 将对象写入磁盘

>>> import pickle
>>> class Player(object):
...     def __init__(self, first, last, position):
...         self.first = first
...         self.last = last
...         self.position = position
...
>>> player = Player('Josh','Juneau','Forward')
>>> pickle_file = open('myPlayer','wb')
>>> pickle.dump(player, pickle_file)
>>> pickle_file.close()

在上面的示例中,我们使用 pickle 模块中的 dump(object, file) 方法将 Player 对象持久化到磁盘。现在让我们将对象读回我们的程序并打印出来。

清单 5-9. 读取和使用腌制后的对象

>>> pickle_file = open('myPlayer','rb')
>>> player1 = pickle.load(pickle_file)
>>> pickle_file.close()
>>> player1.first
'Josh'
>>> player1.last, player1.position
('Juneau', 'Forward')

类似地,我们使用 load(file) 方法将腌制文件读回我们的程序中。读取并存储到变量后,我们可以关闭文件并使用该对象。如果我们必须执行一系列 dumpload 任务,我们可以依次执行,没有任何问题。您还应该知道,有不同的 pickle 协议可用于使 pickle 在不同的 Python 环境中工作。默认协议为 0,但协议 1 和 2 也可供使用。最好坚持使用默认协议,因为它在大多数情况下都能正常工作,但如果您在使用 pickle 处理二进制格式时遇到任何问题,请尝试使用其他协议。

如果我们必须将对象存储到磁盘并在以后引用它们,使用 shelve 模块可能很有意义,该模块充当腌制对象的字典。使用 shelve 技术,您基本上是 pickle 一个对象并使用基于字符串的键值存储它。您以后可以通过将键传递给打开的文件对象来检索该对象。这种技术非常类似于我们对象的归档柜,因为我们始终可以通过键值引用我们的对象。让我们看一下这种技术,看看它是如何工作的。

清单 5-10. 使用 Shelve 技术

# Store different player objects
>>> import shelve
>>> player1 = Player('Josh','Juneau','forward')
>>> player2 = Player('Jim','Baker','defense')
>>> player3 = Player('Frank','Wierzbicki','forward')
>>> player4 = Player('Leo','Soto','defense')
>>> player5 = Player('Vic','Ng','center')
>>> data = shelve.open("players")
>>> data['player1'] = player1
>>> data['player2'] = player2
>>> data['player3'] = player3
>>> data['player4'] = player4
>>> data['player5'] = player5
>>> player_temp = data['player3']
>>> player_temp.first, player_temp.last, player_temp.position
('Frank', 'Wierzbicki', 'forward')
>>> data.close()

在上面的场景中,我们使用了与之前示例中定义的相同的 Player 对象。然后,我们打开了一个新的货架并将其命名为“players”。这个货架实际上包含一组三个文件,这些文件被写入磁盘。一旦对象被持久化到货架中,并且当对对象调用 close() 时,这三个文件可以在磁盘上找到,分别命名为“players.bak”、“players.dat”和“players.dir”。如您所见,我们实例化的所有 Player 对象都已存储到此货架中,但它们存在于不同的键下。我们可以根据需要命名键,只要它们彼此唯一即可。在示例中,我们持久化了五个对象,然后在最后检索并显示了其中一个对象。这是一种制作小型数据存储的非常好的技术。

输出技术

我们在第 2 章中简要介绍了 print 语句,当时讨论了字符串格式化。print 语句是大多数 Python 程序中最常用的输出形式。虽然我们在第 2 章中介绍了一些基础知识,例如转换类型以及如何格式化一行输出,但在这里我们将更深入地介绍 print 语句的一些不同变体以及生成输出的其他技术。print 语句可以使用两种基本格式。我们在第 2 章中介绍了第一种,它使用字符串和一些嵌入在字符串中的转换类型,这些转换类型以百分号 (%) 符号开头。在字符串之后,我们使用另一个百分号 (%) 符号,后跟一个括号列表,其中包含将被替换为字符串中嵌入的转换类型的参数。查看下面示例中每个示例的示例。

清单 5-11. 使用 Print 语句输出

# Using the % symbol
>>> x = 5
>>> y = 10
>>> print 'The sum of %d and %d is %d' % (x, y, (x + y))
The sum of 5 and 10 is 15
>>> adjective = "awesome"
>>> print 'Jython programming is %s' % (adjective)
Jython programming is awesome

您还可以使用嵌入在字符串中的转换类型来格式化浮点输出。您可以使用 ".# of places" 语法在嵌入的转换类型中指定要打印的小数位数。

清单 5-12. 格式化浮点运算

>>> pi = 3.14
>>> print 'Here is some formatted floating point arithmetic: %.2f' % (pi + y)
Here is some formatted floating point arithmetic: 13.14
>>> print 'Here is some formatted floating point arithmetic: %.3f' % (pi + y)
Here is some formatted floating point arithmetic: 13.140

总结

毫无疑问,Python 拥有多种输入输出策略。本章涵盖了大多数这些技术,从基本的终端或命令行 I/O 开始,然后进入文件操作。我们学习了如何使用 open 函数来创建、读取或写入文件。命令行 sys.argv 参数是另一种获取输入的方式,环境变量也可以在我们的程序中使用。在这些主题之后,我们简要地介绍了 pickle 模块以及如何使用它将 Python 对象持久化到磁盘。shelve 模块是使用 pickle 的另一种方式,它允许将多个对象索引并存储在同一个文件中。最后,我们讨论了几种在程序中执行输出的技术。

虽然 I/O 可以写一整本书,但本章只是对 Python 中 I/O 这一广泛主题的良好起点。与本书中讨论的许多 Python 语言细节一样,网上和书籍中有很多资源可以帮助您深入研究这些主题。一个很好的资源是 Magnus Lie Hetland 编写的“Beginning Python: From Novice to Professional”。您也可以查看 Python 文档,可以在 www.python.org/doc/ 找到。