Python读写文本文件
读取文本文件
数据几乎无一例外地是被保存在文件中的。这些文件可能是文本文件、CSV 文件、Excel 文件或其他类型的文件。知道如何访问此类文件以及从中读取数据是在 Python 中进行数据处理、加工与分析的前提。当完成了一个每秒钟可以处理很多文件的程序时,与手动一个个地处理文件相比,你会真正体会到写程序的好处。
你需要告诉 Python,脚本要处理何种类型的文件。你可以在程序中写死文件名称,但是如果这样的话,就不能使用这个程序处理多个不同的文件了。能读取多个不同文件的方法是,在命令行窗口或终端窗口的命令行中,在 Python 脚本的名字后面加上完整的文件路径名。要使用这种方法,需要在脚本开始时导入内置的 sys 模块。在脚本上方加上 import sys 语句之后,就可以在脚本中使用 sys 模块提供的所有功能了:
#!/usr/bin/env python3
from math import exp, log, sqrt
import re
from datetime import date, time, datetime, timedelta
from operator import itemgetter
import sys
导入了 sys 模块之后,你就可以使用 argv 这个列表变量了。这个变量捕获了传递给 Python 脚本的命令行参数列表,即你在命令行中的所有输入,包括你的脚本名称。和任何其他列表一样,argv 也有索引。argv[0] 就是脚本名称,argv[1] 是命令行中传递给脚本的第一个附加参数,在这个例子中,就是 first_script.py 将要读取的文件路径名。
创建文本文件
要读取一个文本文件,首先要创建它。要创建文本文件,需执行以下步骤。
(1) 打开 Spyder IDE 或一个文本编辑器(例如:Windows 系统下的 Notepad、Notepad++、Sublime Text;macOS 系统下的 TextMate、TextWrangler、Sublime Text)。
(2) 在文本文件中写入下面 6 行:
I'm
already
much
better
at
Python.
Notepad++ 中的文本文件 file_to_read.txt(Windows)
(3) 将文件保存在桌面上,文件名为 file_to_read.txt。
(4) 将下面几行代码添加到 first_script.py 的下方:
# 读取文件
# 读取单个文本文件
input_file = sys.argv[1]
print "Output #143: "
filereader = open(input_file, 'r')
for row in filereader:
print row.strip()
filereader.close()
示例中的第一行代码使用 sys.argv 列表捕获了要读取的文件的路径名,并将路径名赋给变量 input_file。第二行代码创建了一个文件对象 filereader,其中包含了以 r 模式(只读模式)打开的 input_file 文件中的各个行。下一行中的 for 循环每次读取 filereader 对象中的一行。for 循环内部的 print 语句打印出每一行,并且在打印之前用 strip 函数去掉每一行两端的空格、制表符和换行符。最后一行代码在输入文件中的所有行都被读取并打印到屏幕后,关闭 filereader 对象。
(5) 重新保存 first_script.py。
(6) 要读取刚才创建的文本文件,输入下面的命令,如图所示,然后按回车键:
python first_script.py file_to_read.txt
Python 脚本和它要在命令行窗口中处理的文本文件
这样,你就在 Python 中读取了一个文本文件。你会看到下面的内容被打印到屏幕上,在以前的输出之后:
I'm
already
much
better
at
Python.
first_script.py 的输出,在命令行窗口中处理文本文件
脚本和输入文件在同一位置
因为 first_script.py 和 file_to_read.txt 在同一位置,即都在桌面上,所以简单地输入 python first_script.py file_to_read.txt 是可以的。如果文本文件和脚本不在同一位置,就需要输入文本文件的完整路径名,这样脚本才能知道去哪里寻找这个文件。
例如,如果文本文件在你的 Documents 文件夹中,而不是在桌面上,那么你可以在命令行中使用下面的路径名来从其所处位置读取文本文件:
python first_script.py "C:\Users\[Your Name]\Documents\file_to_read.txt"
读取文件的新型语法
前面讲的用来创建 filereader 对象的那行代码是创建文件对象的传统方法。这种方法没有什么问题,但是它使文件对象一直处于打开状态,直到使用 close 函数明确地关闭或直到脚本结束。尽管这种做法一般没有问题,但不够清晰,还被证明在更复杂的脚本中会导致错误。从 Python 2.5 开始,你可以使用 with 语句来创建文件对象。这种语法在 with 语句结束时会自动关闭文件:
input_file = sys.argv[1]
print("Output #144:")
with open(input_file, 'r', newline='') as filereader:
for row in filereader:
print("{}".format(row.strip()))
你可以看到,使用 with 语句的版本与前一个版本非常相似,但是它不需调用 close 函数来关闭 filereader 对象。
这个示例演示了如何使用 sys.argv 来访问并打印一个文本文件中的内容。这是一个简单的示例,但在后面的示例中,要以此为基础访问其他类型的文件,或一次访问多个文件,并向输出文件中写入内容。
下面介绍 glob 模块,它让你能够通过几行代码读取和处理多个输入文件。glob 模块之所以功能强大,是因为它处理的是文件夹(也就是说,它处理目录,不是单个的文件),所以将前面读取文件的代码删除或注释掉,这样就可以使 argv[1] 指向一个文件夹,而不是一个文件了。将代码注释掉就是在你希望计算机忽略掉的代码前面加上一个井号,所以当你结束注释时,first_script.py 文件就应该像下面这样:
## 读取一个文本文件(旧方法) ##
#input_file = sys.argv[1]
#print("Output #143:")
#filereader = open(input_file, 'r', newline='')
#for row in filereader:
# print("{}".format(row.strip()))
#filereader.close()
## 读取一个文本文件(新方法) ##
#input_file = sys.argv[1]
#print("Output #144:")
#with open(input_file, 'r', newline='') as filereader:
# for row in filereader:
# print("{}".format(row.strip()))
做完这些修改之后,你就可以添加下一节要讨论的 glob 代码来处理多个文件了。
使用glob读取多个文本文件
在很多商业应用中,需要对多个文件进行同样的或相似的处理。例如,你可能会从多个文件中选择数据子集,根据多个文件计算像总计和均值这样的统计量,或根据来自于多个文件的数据子集计算统计量。当文件数量增加时,手动处理文件的可能性会减小,出错的概率会增加。
读取多个文件的一种方法是在命令行中将包含输入文件目录的路径名写在 Python 脚本名称之后。要使用这种方法,你需要在脚本开头导入内置的 os 模块和 glob 模块。在脚本上方添加了 import os 和 import glob 语句之后,你就可以使用 os 模块和 glob 模块提供的所有功能了:
#!/usr/bin/env python3
from math import exp, log, sqrt
import re
from datetime import date, time, datetime, timedelta
from operator import itemgetter
import sys
import glob
import os
当导入了 os 模块之后,你就可以使用它提供的若干种路径名函数了。例如,os.path.join 函数可以巧妙地将一个或多个路径成分连接在一起。glob 模块可以找出与特定模式相匹配的所有路径名。os 模块和 glob 模块组合在一起使用,可以找出符合特定模式的某个文件夹下面的所有文件。
要读取多个文件,需要再创建一个文本文件。
创建另一个文本文件
(1) 打开 Spyder IDE 或一个文本编辑器(例如:Windows 系统下的 Notepad、Notepad++、Sublime Text;macOS 系统下的 TextMate、TextWrangler、Sublime Text)。
(2) 在文本文件中写入下面 8 行):
This
text
comes
from
a
different
text
file.
Notepad++ 中的文本文件 another_file_to_read.txt
(3) 将文件保存在桌面上,文件名为 another_file_to_read.txt。
(4) 将下面几行代码添加到 first_script.py 的下方:
# 读取多个文本文件
print("Output #145:")
inputPath = sys.argv[1]
for input_file in glob.glob(os.path.join(inputPath,'*.txt')):
with open(input_file, 'r', newline='') as filereader:
for row in filereader:
print("{}".format(row.strip()))
这个示例中的第一行代码与读取单个文本文件示例中的代码非常相似,只是在这个示例中,要提供一个目录路径名,而不是一个文件路径名。这里,要提供的路径指向包含了两个文本文件的目录。
第二行代码是 for 循环,使用 os.path.join 函数和 glob.glob 函数来找出符合特定模式的某个文件夹下面的所有文件。指向这个文件夹的路径包含在变量 inputpath 中,这个变量将在命令行中被提供。os.path.join 函数将这个文件夹路径和这个文件夹中所有符合特定模式的文件名连接起来,这种特定模式可以由 glob.glob 函数扩展。这个示例使用的是模式 *.txt 来匹配由 .txt 结尾的所有文件名。因为这是一个 for 循环,所以这行中其余的代码你应该很熟悉了。input_file 是一个占位符,表示由 glob.glob 函数生成的列表中的每个文件。这行代码的意义就是:对于匹配文件列表中的每个文件,做下面的操作……
余下的代码和读取单个文件的代码非常相似。以只读方式打开 input_file 变量,然后创建一个 filereader 对象。对于 filereader 对象中的每一行,除去行两端的空格、制表符和换行符,然后打印这一行。
(5) 重新保存 first _script.py。
(6) 要读取这些文本文件,输入以下代码,如图所示,然后按回车键:
python first_script.py "C:\Users\[Your Name]\Desktop"
Python 脚本和指向包含文本文件的桌面文件夹的路径
这样,你就在 Python 中读取了多个文本文件。你会看到以下内容被打印到屏幕上,在以前的输出之后:
This
text
comes
from
a
different
text
file.
I'm
already
much
better
at
Python.
first_script.py 的输出,在命令行窗口中处理多个文本文件
学会这项技术的一个巨大好处是它可以规模化扩展。这个示例只是处理两个文本文件,但是它可以轻松地扩展为处理几十、几百或者几千甚至更多的文件。学习了如何使用 glob.glob 函数,仅花费手动处理的一小部分时间,就可以处理非常非常多的文件。
写入文本文件
迄今为止,大多数示例还是使用 print 语句将输出发送到命令行窗口或终端窗口。当你在调试程序,或者在检查输出的准确度时,将输出打印到屏幕上是有意义的。但是,在很多情况下,只要你能确定输出是正确的,就会需要将输出写入文件,以进行更进一步的分析、报告和存储。
Python 提供了两种简单的方法来将输出写入文本文件和分隔符文件。write 方法可将单个字符串写入一个文件,writelines 方法可将一系列字符串写入一个文件。下面的两个示例使用 range 函数和 len 函数跟踪一个列表中的索引值,以将分隔符放在各个列表值之间,并在最后一个列表值后面放上一个换行符。
向first_script.py添加代码
(1) 将下面各行代码添加到 first_script.py 的底部:
# 写入文件
# 写入一个文本文件
my_letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
max_index = len(my_letters)
output_file = sys.argv[1]
filewriter = open(output_file, 'w')
for index_value in range(len(my_letters)):
if index_value < (max_index-1):
filewriter.write(my_letters[index_value]+'\t')
else:
filewriter.write(my_letters[index_value]+'\n')
filewriter.close()
print "Output #146: Output written to file"
在这个例子中,变量 my_letters 是一个字符串列表。这里想把这些字母打印到一个文本文件中,每个字母之间用制表符分隔。这个示例中的难点是确保在字母之间以制表符分隔,并在最后一个字母后面放上一个换行符(不是制表符)。
为了知道什么时候到达最后一个字母,你需要跟踪列表中字母的索引值。len 函数用来计算出列表中字母的数量,所以 max_index 等于 10。在命令行窗口或终端窗口中,再次使用 sys.argv[1] 来在命令行中提供输出文件的路径名。创建一个文件对象 filewriter,但是打开方式不是只读,而是通过 w(可写)的方式打开。使用 for 循环在列表 my_letters 的各个值之间进行迭代,并使用 range 函数和 len 函数跟踪列表中各个字母的索引值。
if-else 语句可以使你对列表中的最后一个字母做出与前面那些字母不同的处理。if-else 语句是这样工作的:my_letters 包含 10 个元素,但是索引从 0 开始,所以各个字母的索引值分别是 0、1、2、3、4、5、6、7、8、9。因此,my_letters[0] 是 a,my_letters[9] 是 j。if 代码块判断索引值 x 是否小于 9,max_index - 1 或者是 10 - 1 = 9。直到列表中的最后一个字母,这个条件才为 True。因此,if 代码块的意义是:一直到列表中的最后一个字母,都向输出文件中写入字母,并在字母后面加一个制表符。当你到达了列表中的最后一个字母时,这个字母的索引值为 9,不大于 9,所以 if 代码块判断为 False,就执行 else 代码块。else 代码块中的 write 语句的意义是:向输出文件中写入最后一个字母,并在后面加一个换行符。
(2) 将前面读取多个文件的代码注释掉。
为了看到这些代码是如何工作的,这里需要写入一个文件然后查看输出。因为你又一次使用了 argv[1] 来确定输出文件的路径名,所以需要将前面的 glob 代码删除或注释掉,这样就可以使用 argv[1] 来确定输出文件了。如果选择注释掉前面的 glob 代码,那么 first_script.py 应该如下所示:
## 读取多个文本文件
#print("Output #145:")
#inputPath = sys.argv[1]
#for input_file in glob.glob(os.path.join(inputPath,'*.txt')):
# with open(input_file, 'r', newline='') as #filereader:
# for row in filereader:
# print("{}".format(row.strip()))
(3) 重新保存 first_script.py。
(4) 要写入一个文本文件,输入下面的代码,如图所示,然后按回车键:
python first_script.py "C:\Users\[Your Name]\Desktop\write_to_file.txt"
应该在命令行窗口中输入的 Python 脚本、文件路径和输出文件名
(5) 打开输出文件 write_to_file.txt。
这样,你就使用 Python 将输出写入了一个文本文件。在完成这些步骤之后,你不会在屏幕上看到新的输出;但是,如果你将所有打开的窗口最小化,就会看到桌面上有一个新的文本文件,名为 write_to_file.txt。这个文件中应该包含了列表 my_letters 中的字母,以制表符隔开,最后有一个换行符,如图所示。
输出文件 write_to_file.txt,由 first_script.py 在桌面上创建
下一个示例与这个很相似,只是它演示了如何使用 str 函数来将元素转换为字符串,以便使用 write 函数将其写入一个文件。它还演示了使用 ‘a’(追加)方式将输出追加到一个已经存在的输出文件末尾的方法。
写入CSV文件
(1) 将下列各行代码添加到 first_script.py 的底部:
# 写入CSV文件
my_numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
max_index = len(my_numbers)
output_file = sys.argv[1]
filewriter = open(output_file, 'a')
for index_value in range(len(my_numbers)):
if index_value < (max_index-1):
filewriter.write(str(my_numbers[index_value])+',')
else:
filewriter.write(str(my_numbers[index_value])+'\n')
filewriter.close()
print "Output #147: Output appended to file"
这个示例与前面的示例非常相似,但是它说明了如何向已经存在的输出文件中追加内容,以及如何将列表中的非字符串数据转换成字符串,以便可以使用 write 函数来写入文件。在这个示例中,列表中的元素是整数。write 函数处理的是字符串,所以在你使用 write 函数将其写入输出文件之前,需要使用 str 函数将非字符串数据转换成字符串。
在使用 for 循环进行第一次迭代时,str 函数会向输出文件中写入一个 0,然后写入一个逗号。以这种方式继续写入列表中的其他数值,直到列表中的最后一个数值,这时执行 else 代码块,将最后一个数值写入输出文件,并在后面加上一个换行符,而不是逗号。
请注意在打开文件对象 filewriter 时,使用的是追加模式(‘a’),而不是可写模式(‘w’)。如果在命令行中提供了同样的输出文件名,那么这段代码的输出会被追加到 write_to_file.txt 文件中,在以前写入文件的内容之后。相反,如果使用可写方式打开 filewriter 对象,那么以前的输出会被删除,write_to_file.txt 文件中只会出现这段代码的输出。你会在本书后面的章节中看到使用追加方式打开文件的作用,这时你要处理多个文件,并将其中所有的数据追加到一个连接文件中。
(2) 重新保存 first_script.py。
(3) 要向文本文件中追加数据,输入以下命令然后按回车键:
python first_script.py "C:\Users\[Your Name]\Desktop\write_to_file.txt"
(4) 打开输出文件 write_to_file.txt。
这样,你就使用 Python 向文本文件中写入和追加了数据。在完成这些步骤之后,你不会在屏幕上看到新的输出;但是,如果你打开了 write_to_file.txt 文件,会看到文件中出现了新的一行,行中包括了 my_numbers 中的数值,以逗号隔开,并在末尾有一个换行符,如图 所示。
输出文件 write_to_file.txt,由 first_script.py 追加了信息
最后,这个示例演示了一个写入 CSV 文件的有效方法。实际上,在前面的例子中,你将由制表符分隔的数据写入了输出文件,如果将制表符改为逗号,并且将输出文件命名为 write_to_file.csv 而不是 write_to_file.txt 的话,就可以创建一个 CSV 文件。