10.1 从文件中读取数据
要使用文本信息,首先需要将信息读取到内存中。为此,你可以一次性读取文件的全部内容,也可以每次一行的方式逐步读取。
10.1.1 读取整个文件
要读取文件,需要一个包含几行文本的文件。首先创建一个文件pi_digits.txt,它包含精确到小数点后30位的圆周率值,且在小数点后10位处都换行:
下面的程序打开并读取这个文件,再将其内容显示到屏幕上:
with open('pi_digits.txt') as file_object: contents = file_object.read() print(contents)
关键字with在不再需要访问文件后将其关闭。你也可以调用open()和close()来打开和关闭文件,但这样做,如果程序勋在bug,导致close语句未执行,文件将不会关闭。如果使用前面所示的结构,可让Python去确定:你只管打开文件,并在需要的时候使用它,Python自会在合适的时候自动将其关闭。
输出结果:
书上说,因为read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除末尾的空行,可在print语句中使用rstrip():
但是,但是,但是!笔者用了这个方法发现输出并没有任何变化。菜鸟的我纠结了很久,依然没能解惑。(下面有惊喜!在动手试一试里笔者重新认识了这个问题。)
10.1.2 文件路径
根据你组织文件的方式,有时可能要打开不在程序文件所属目录中的文件。要让Python打开不与程序文件同一个目录中的文件,需要提供文件路径,它让Python到系统的特定位置去查找。
例如,文件夹text_files位于文件夹python_work中,因此可使用相对路径来打开文件夹中的文件。相对文件路径让Python到指定位置去找,而该位置时相对于当前允运行的程序所在所在目录的。在Windows系统中:with open('text_filesfilename.txt) as file_object
也可以将文件再计算机中的准确位置告诉Python,这称为绝对文件路径。在相对路径行不通时,可以使用绝对路径。
例如,text_files文件夹并不在文件夹python_work中,而在文件夹other_files中,则向open()传递路径'text_file/filename.txt'行不通,因为Python只在文件夹python_work中查找该位置。绝对路径通常比相对路径更长,因此将其存储在一个变量中,再将该变量传递给open()会有所帮助。在Windows系统中:
file_path = 'C:Usersehmatthesother_files ext_filesfilename.txt'
with open(file_path) as file object:
注意,在Windows中确保在文件路径中使用的是反斜杠。另外,由于反斜杠在Python中被视为转义标记,为Windows中确保万无一失,应以原始字符串的方式指定路径,即在开头的单引号前加r。
10.1.3 逐行读取
要以每一次一行的方式检查文件,可对文件对象使用for循环:
filename = 'pi_digits.txt' with open(filename) as file_object: for line in file_object: print(line)
输出结果:
使用了rstrip()之后,多余的空行就消除了:
10.1.4 创建一个包含文件各行内容的列表
使用关键字with时,open()返回的文件对象只在with代码块内可用。如果要在with代码块外访问文件的内容,可在with代码块内将文件的各行存储在一个列表中,并在with代码块外使用该列表:你可以立即处理文件的各个部分,也可推迟到程序后面再处理。
filename = 'pi_digits.txt' with open(filename) as file_object: lines = file_object.readlines() for line in lines: print(line.rstrip())
输出的结果与文件内容完全一致。
10.1.5 使用文件的内容
创建一个字符串,包含文件中存储的所有数字,且没有空格:
filename = 'pi_digits.txt' with open(filename) as file_object: lines = file_object.readlines() pi_string = '' for line in lines: pi_string +=line.rstrip() print(pi_string) print(len(pi_string))
其中,rstrip()用来删除每行末尾的换行符。打印这个字符串及其长度:
3.1415926535 8979323846 2643383279 36
在变量pi_string存储的字符串中,包含原来位于每行左边的空格,为删除这些空格,可使用strip()而不是rstrip()。因此将rstrip()替换成strip。
输出结果:
3.141592653589793238462643383279 32
注意,读取文本文件时,Python将其中所有的文本都解读为字符串。如果想要读取数字,并要将其作为数值使用,就必须使用函数int()将其转换为整数,或使用函数float()将其转换为浮点数。
10.1.6 包含一百万位的大型文件
对于你可以处理的数据量,Python没有任何限制;只要系统内存足够多,你想处理多少数据都可以。
10.1.7 圆周率包含你的生日吗
filename = 'pi_digits.txt' with open(filename) as file_object: lines = file_object.readlines() pi_string = '' for line in lines: pi_string +=line.strip() birthday = input("Enter your birthday,in the form mmddyy: ") if birthday in pi_string: print("Your birthday appears in the first million digits of pi!") else: print("Your birthday does not appear in the first million digits of pi!")
笔者用的是某香的生日:(你的生日不在pi里哦)
Enter your birthday,in the form mmddyy: 051397 Your birthday does not appear in the first million digits of pi!
P169 动手试一试 10-1 在文本编辑器中新建文件,命名为‘learning_python.txt’,分别用上面学到的知识打印三次。
filename = 'learning_python.txt' print("First print:") with open(filename) as a: contexts = a.read() print(contexts) print("Second print:") with open(filename) as b: for line in b: print(line.rstrip()) print("Third print:") with open(filename) as c: lines = c.readlines() for line in lines: print(line.rstrip())
笔者写到这边看运行结果的时候终于发现了rstrip()的功能。由于我第一打印没有用rstrip()所以输出是这样的:
10-2 使用replace()方法将字符串中的特定单词替换。
filename = 'learning_python.txt' print("First print:") with open(filename) as a: contexts = a.read() print(contexts.rstrip().replace('Python','c')) print("Second print:") with open(filename) as b: for line in b: print(line.rstrip().replace('python','c')) print("Third print:") with open(filename) as c: lines = c.readlines() for line in lines: print(line.rstrip().replace('Python','c'))
输出结果:
可以看出replace()方法在使用时,区分大小写。
10.2 写入文件
10.2.1 写入空文件
要将文件写入文件,在调用open()时需要提供另一个实参,告诉Python你要写入打开的文件。
filename = 'programming.txt' with open(filename,'w') as f: f.write("I love programming.")
在这个示例中,调用open()时提供了两个实参。第一个实参是要打开的文件的名字;第二个实参(‘w’)告诉Python,我们要以写入模式打开这个文件。打开文件时,可指定读取模式(‘r’)、写入模式(‘w’)
附加模式(‘a’)或能够读取和写入文件的模式(‘r+’)。如果省略了模式的实参,Python将以默认的只读模式打开文件。
如果你要写入的文件不存在,函数open()将自动创建它。然而,以写入模式打开文件时要小心,因为如果指定的文件以及存在,Python将在返回文件对象前清空该文件。
注意,Python只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数str()将其转换为字符串格式。
10.2.2 写入多行
函数write()不会自动在文本末尾添加换行符。要让每个字符串都单独占一行,,需要在write()语句中包含换行符。
10.2.3 附加到文件
如果你要给文件添加内容,而不是覆盖原来的内容,可以附加模式打开文件。如果指定的文件不存在,Python将为你创建一个空文件。
p172动手试一试 10-4
filename = 'guest.txt'
with open(filename,'w') as f:
message = ''
while message !='quit':
message = input("please input your name:")
print("hello,"+message+" you can enter quit to exit.")
if message !='quit':
f.write(message+' ')
运行结果:
10.3 异常
Python使用被称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继续运行;如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告。
异常是使用try-except代码块处理的。try-except代码块让Python执行指定的操作,同时告诉Python发生异常时怎么办。使用了try-except大妈快时,几遍出现异常,程序也将继续运行:显示你编写好的有好的错误的信息,而不是令用户迷惑的traceback。
10.3.1 使用try-except代码块处理ZeroDivisionError异常
try: print(5/0) except ZeroDivisionError: print("You can't divide by zero!")
如果try代码块中的代码运行起来没有任何问题,Python将跳过except代码块;如果try代码块中的代码引发了错误,Python将查找这样的except代码块,并运行其中的代码,即其中指定的错误与引发的错误相同。上面的例子输出:
You can't divide by zero!
10.3.2 else 代码块
发生错误是,如果程序还有工作没有做完,如果能妥善处理,阻止程序崩溃是最好的。
print("Give me two numbers,and I'll divide them.") print("Enter 'q' to quit.") while True: first_number = input(" First number:") if first_number == 'q': break second_number=input("Second number:") try: answer = int(first_number) / int(second_number) except ZeroDivisionError: print("You can't divide by zero!") else: print(answer)
输出结果:
Give me two numbers,and I'll divide them. Enter 'q' to quit. First number:5 Second number:0 You can't divide by zero! First number:5 Second number:2 2.5 First number:q
10.3.3 处理
使用文件时,一种常见的问题时找不到文件。
filename = 'd2wvc' with open(filename) as f: con = f.read() File "D:/PycharmProject/Study/Chapter10.py", line 82, in <module> with open(filename) as f: FileNotFoundError: [Errno 2] No such file or directory: 'd2wvc'
这个错误是open()导致的,因此要处理这个额错误,必须将try语句放在包含open()的代码之前。
filename = 'd2wvc' try: with open(filename) as f: con = f.read() except FileNotFoundError: msg = 'Sorry,the file '+ filename +"does not exist." print(msg)
输出结果:
Sorry,the file d2wvcdoes not exist.
如果文件不存在,这个程序什么都不做,处理的意义不大。
10.3.4 分析文本
下面来提取通话Alice in Worderland 的文本,并尝试计算它包含多少个单词。将使用到split(),它根据一个字符串创建一个单词列表。
filename = 'alice.txt' try: with open(filename) as f: con = f.read() except FileNotFoundError: msg = "Sorry,the file "+ filename + "does not exist." print(msg) else: #计算文件大致包含多少个单词 words = con.split() num_words = len(words) print("The file " + filename + "has about " + str(num_words) + " words")
输出了alice.txt包含多少个单词:
The file alice.txthas about29461 words
10.3.5 使用多个文件
def count_words(filename): """计算一个文件大致包含多少个单词""" try: with open(filename) as f: con = f.read() except FileNotFoundError: msg = "Sorry,the file "+ filename + " does not exist." print(msg) else: #计算文件大致包含多少个单词 words = con.split() num_words = len(words) print("The file " + filename + "has about " + str(num_words) + " words") filenames = ['alice.txt','siddhartha.txt','moby_dict.txt','little_women.txt'] for filename in filenames: count_words(filename)
其中文件siddhartha.txt不存在,但丝毫不影响这个程序处理其他文件:
The file alice.txthas about 29461 words Sorry,the file siddhartha.txt does not exist. The file moby_dict.txthas about 215136 words The file little_women.txthas about 189079 words
如果希望,在运行失败时,程序一声不吭,就可以将except代码块处,修改为pass,则没有任何输出。
p179动手试一试 10-7 加法计算器
print("Give me two numbers,and I'll add them.") print("Enter 'q' to quit.") while True: first_num = input("first num:") if first_num == 'q': break sec_num = input("second num:") if sec_num == 'q': break try: answer =int(first_num) + int(sec_num) except ValueError: print("input must be a number") else: print(answer)
运行测试:
Give me two numbers,and I'll add them. Enter 'q' to quit. first num:wefd second num:de2 input must be a number first num:1 second num:1 2 first num:q
p179动手试一试 10-9 沉默的猫和狗,其中dog.txt未创建
def show_con(filename): try: with open(filename) as f: con = f.read() except FileNotFoundError: pass else: print(" Reading file: " + filename) print(con) filenames = ['dog.txt','cat.txt'] for filename in filenames: show_con(filename)
运行结果:
Reading file: cat.txt
kitty
hello
white
p179动手试一试 10-10常见的单词,计算出各个文件中‘the’的个数。(不分大小写)
def count_the(filename): try: with open(filename) as f: con = f.read() except FileNotFoundError: msg = "Sorry,the file "+ filename + " does not exist." print(msg) else: num = con.lower().count('the') print(num) filenames = ['alice.txt','siddhartha.txt','moby_dict.txt','little_women.txt'] for filename in filenames: count_the(filename)
输出结果:
2505 Sorry,the file siddhartha.txt does not exist. 20028 11842
10.4 存储数据
很多程序都要求用户输入某种信息,如让用户存储游戏首选项或提供可视化的数据。不管专注的是什么,程序都把用户提供的信息存储在列表和字典等数据结构中。用户关闭程序时,你几乎总是要保存他们提供的信息;一种简单的方式使用json来存储数据。
模块json让你能够将简单的Python数据结构转存储到文件中,并在程序再次运行时加载该文件中的数据。你还可以使用json在Python程序之间分享数据。
10.4.1 使用 json.dump()和json.load()
我们来编写一个存储一组数字的简短程序,再编写一个将这些数字读取到内存中的程序。第一个程序将使用json.dump()来存储这组数字,而第二个程序将使用json.load()。
函数json.dump()接收两个实参:要存储的数据以及可用于存储数据的文件对象。
import json nums = [2,3,4,11] filename = 'nums.json' with open(filename,'w') as f: json.dump(nums,f)
这个程序没有输出,但可以打开nums.json , 数据的存储格式与在Python中一样。
[2, 3, 4, 11]
下面再编写一个程序,使用json.load()将这个列表读取到内存中:
import json nums = [2,3,4,11] filename = 'nums.json' with open(filename,'w') as f: json.dump(nums,f) with open(filename) as f: numbers = json.load(f) print(numbers)
打印出的数字列表与文件中创建的一样,这是一种在程序间共享数据的简单的方式。
10.4.2 保存和读取用户生成的数据
对于用户生成的数据,使用json保存它们,程序停止数据也不会丢失。下面来看一个例子:用户首次运行程序时被提示输入自己的名字,这样再次运行程序时就记住他了。
我们首先来存储用户的名字:
import json username = input("What is your name? ") filename = 'username.json' with open(filename,'w') as f: json.dump(username,f) print("We'll remember you when you come back," + username + "!")
运行结果:
What is your name? Eric We'll remember you when you come back,Eric!
现在再编写一个程序,向其名字呗存储的用户发出问候,再将两个程序合并:
import json # 如果以前存储了用户名,就加载它 # 否则,就提示用户输入用户名并存储它 filename = 'username.json' try: with open(filename) as f: username = json.load(f) except FileNotFoundError: with open(filename, 'w') as f: username = input("What is your name? ") json.dump(username, f) print("We'll remember you when you come back," + username + "!") else: print("Welcome back, " + username + "!")
输出结果:
Welcome back, Eric!
10.4.3 重构
代码能够正确运行,但可做进一步的改进——将代码划分为一系列完成具体工作的函数。这样的过程被称为重构。重构让代码更加清晰、更容易理解、更容易扩展。
要重构用上面的代码,可将其放在一个或多个函数中,我们将它放在greet_user()中:
import json def greet_user(): """问候用户,并指出其姓名""" filename = 'username.json' try: with open(filename) as f: username = json.load(f) except FileNotFoundError: with open(filename, 'w') as f: username = input("What is your name? ") json.dump(username, f) print("We'll remember you when you come back," + username + "!") else: print("Welcome back, " + username + "!") greet_user()
下面来重构greet_user(),让它不执行这么多任务。为此我们将获取存储的用户名的戴拿移到另一个函数中:
import json def get_stored_username(): """如果存储了用户名,就获取它""" filename = 'username.json' try: with open(filename) as f: username = json.load(f) except FileNotFoundError: return None else: return username def greet_user(): """问候用户,并指出其姓名""" username = get_stored_username() if username: print("Welcome back, " + username + "!") else: username = input("What is your name? ") filename = 'username.json' with open(filename,'w') as f: json.dump(username,f) print("We'll remember you when you come back," + username + "!") greet_user()
新增的函数目标明确,如果存储了用户名,这个函数就获取并返回它;如果文件username.json不存在,那这个函数就返回None。
同理,也可将great_user()中的另一个代码提取出来:将没有存储用户名时提示用户输入代码的代码放在一个独立的函数中。
import json def get_stored_username(): """如果存储了用户名,就获取它""" filename = 'username.json' try: with open(filename) as f: username = json.load(f) except FileNotFoundError: return None else: return username def get_new_username(): """提示用户输入用户名""" username = input("What is your name? ") filename = 'username.json' with open(filename, 'w') as f: json.dump(username, f) return username def greet_user(): """问候用户,并指出其姓名""" username = get_stored_username() if username: print("Welcome back, " + username + "!") else: username = get_new_username() print("We'll remember you when you come back," + username + "!") greet_user()
p179动手试一试 10-13 验证用户
import json def get_stored_username(): """如果存储了用户名,就获取它""" filename = 'username.json' try: with open(filename) as f: username = json.load(f) except FileNotFoundError: return None else: return username def get_new_username(): """提示用户输入用户名""" username = input("What is your name? ") filename = 'username.json' with open(filename, 'w') as f: json.dump(username, f) return username def greet_user(): """问候用户,并指出其姓名""" username = get_stored_username() if username: correct = input("Are you " + username + "? (y/n) ") if correct == 'y': print("Welcome back, " + username + "!") return username = get_new_username() print("We'll remember you when you come back, " + username + "!") greet_user()
测试运行结果:
Are you Eric? (y/n) n What is your name? pong We'll remember you when you come back, pong!
再运行一次:
Are you pong? (y/n) y
Welcome back, pong!