Python学习之路9-文件和异常

《Python编程:从入门到实践》笔记。
本章主要是学习Python的文件操作,主要是从文件中读取数据以及将数据存储到文件中,还有错误处理,异常类,json模块等。

1. 从文件中读数据

1.1 读取整个文件

以下文件pi_digits.txt包含了精确到小数点后30位的圆周率数据

1
2
3
4
5
6
7
8
9
10
11
# pi_digits.txt文件
3.1415926535
8979323846
2643383279

# 代码:
with open("pi_digits.txt", "r") as file_object:
contents = file_object.read() # 一次性读取整个文件
print(contents)

# 结果:和上述文件内容一样

从上述代码可以看出,我们打开文件使用open()函数,该函数至少接收一个参数,即文件路径。读取文件时需要向open()函数指明是用什么方式读取文件,是只读("r"),只写("w"),末尾添加("a")还是读写均可("r+"),open()函数默认以“只读”方式读取文件。这只是4中常用的文件读取方式,此外还有至少8种读写方式。open()函数返回一个文件对象,file_object用于接收该对象。通过文件对象的read()方法读取文件内容,且该方法返回整个文件的内容。

上述代码中的文件和源代码在同一目录中。注意文件路径的问题,绝对路径(不提倡)和相对路径(相对于源文件的路径)以及Windows和Linux下路径的写法。

注意代码中的with关键字。其实读写文件不需要该关键字,打开文件使用open()函数,文件读取完后关闭文件使用close()函数,读取内容可以调用read()方法。而之所以使用with关键字,主要是因为①你最后忘记关闭文件,就想忘了关灯一样;②也可能是在关闭前程序出错,导致close()语句未执行。这些让文件没有关闭的情况都有可能导致数据丢失或损坏。with关键字则被用来应对这些情况,它保证在结束with块时,文件一定会被关闭。

1.2 逐行读取

上述代码一次性读取整个文件,这在文件较小或者内存充裕的时候没有问题,但如果文件特别大,内存容量又很羞涩,则只能逐行读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 代码:
file_name = "pi_digits.txt"

with open(file_name) as file_pi:
for line in file_pi: # 也可以通过while循环配合readline()方法逐行读取文件
print(line)

# 结果:
3.1415926535

8979323846

2643383279

这里需要注意一个问题,就是对行以及文件末尾空字符的读取问题,read()readline()方法会读取末尾的空字符(这里是换行符)。我们可以通过之前讲的rstrip()方法去掉末尾的空字符。

1.3 将文件每一行放入列表中

readlines()方法将文件中每一行存入列表并返回,以下代码进一步处理文件中的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 代码:
file_name = "pi_digits.txt"

with open(file_name) as file_pi:
lines = file_pi.readlines()

pi_string = ""
for line in lines:
pi_string += line.strip()

print(pi_string)
print(len(pi_string))

# 结果:
3.141592653589793238462643383279
32

注意,Python从文件中读取出的所有内容都是字符串,如果你想要的是数字,请记得转换。

2. 写入文件

以下是一个简单的文件写入程序:

1
2
3
filename = "python.txt"
with open(filename, "w") as file_obj:
file_obj.write("I love python!")

执行改代码后你会看到在同一目录下会生成一个名为“python.txt”的文件。需要注意的是,以"w"方式打开文件,如果要写入的文件不存在,则会自动创建该文件;如果该文件存在,该文件的内容会被清空,然后再写入。如果不想文件被清空,请使用"a"(文件指针放在文件末尾)或"r+"(文件指针指向文件开头)方式打开文件。还有一点,write()函数不会在文件末尾添加换行符,如果需要换行符,请自行添加。

3. 异常

Python中使用被称为异常的特殊对象来管理程序执行期间发生的错误。每当代码运行时如果遇到了不能处理的错误,Python都会创建一个异常对象,如果程序中没有处理该对象的相关代码,程序将会停止,并显示一个traceback,其中包含异常的相关报告。如果不想程序因为某些异常而终止运行,则需要我们使用try-except代码块自行处理异常。以下是一个处理除零错误ZeroDivisionError的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 代码:
# 捕捉异常
try:
resule = 5 / 0
except ZeroDivisionError:
print("You can't divide by zero!\n")

# 不捕捉异常
print(5 / 0)

# 结果:
You can't divide by zero!

Traceback (most recent call last):
File "division.py", line 29, in <module>
print(5 / 0)
ZeroDivisionError: division by zero

如果你打算编写一个计算器应用,那么这段代码必不可少。第一个例子表明,即使发生了异常,只要异常被我们捕捉,那么程序便不会终止。如果只想捕捉异常,但暂时又不想处理,可以将上述的print("You can't divide by zero!\n")替换为pass语句。如果想捕获所有的异常,则except后面不指定异常类型。

3.1 else代码块

try-except代码块还可以和else语句组合形成try-except-else代码块,该结构表示,如果捕获了异常,这执行except中的程序,没有发生异常则执行else中的程序。以下程序是一个循环统计文件中单词数的例子,文件读取的部分被放到了函数中,该函数检测有没有发生FileNotFoundError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 代码:
def count_words(filename):
"""计算一个文件大致包含多少个单词"""
try:
with open(filename) as f_obj:
contents = f_obj.read()
except FileNotFoundError:
msg = "Sorry, the file" + filename + " does not exist."
print(msg)
else:
# 计算文件大只包含多少个单词
words = contents.split()
num_words = len(words)
print("The file " + filename + " has about " + str(num_words) + "words.")

filenames = ["alice.txt", "siddhartha.txt", "moby_dick.txt", "little_women.txt"]
for filename in filenames:
count_words(filename)

# 结果:
The file alice.txt has about 29461 words.
Sorry, the file siddhartha.txt does not exist.
The file moby_dick.txt has about 215136 words.
The file little_women.txt has about 189097 words.

else的补充:其实else不光可以和iftry-except结合,还可以和for循环和while循环结合,比如:

1
2
3
4
5
6
7
8
9
10
for i in range(10):
pass
else:
pass

i = 0
while i < 10:
i++
else:
pass

这里的else表示当循环结束后执行一些语句,比如提示之类的。

3.2 决定报告哪些错误

编写得很好且经过详尽测试的代码不容易出现内部错误,如语法或逻辑错误,但只要程序依赖于外部因素,如用户输入、存在指定的文件、有网络连接等,就有可能出现异常。凭经验可判断改在程序的什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息。

4. 存储数据

很多程序要求用户输入某种信息,也有可能程序中某些变量的数据在程序结束后不能丢失(比如机器学习最后训练出来的模型参数),这是就需要将这些信息以文件的形式存下来。存储数据的方式有很多,现在比较简单且通用的是使用json来存储信息。json(JavaScript Object Notation)格式最初是为JavaScript 开发的,但随后成了一种常见格式,并被包括Python在内的众多语言采用。以下是一个经过了重构的存储用户信息的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import json

def get_stored_username(filename):
"""如果存储了用户名,就获取它"""
try:
with open(filename) as f_obj:
username = json.load(f_obj)
except FileNotFoundError:
return None
else:
return username

def get_new_username(filename):
"""提示用户输入用户名,并存入文件"""
username = input("What's your name?")
with open(filename, "w") as f_obj:
json.dump(username, f_obj)
return username

def greet_user(filename):
"""向用户打招呼"""
username = get_stored_username(filename)
if username:
print("Welcome back, " + username + "!")
else:
username = get_new_username(filename)
print("We'll remember you when you come back, " + username + "!")

filename = "username.json"
greet_user(filename)

代码就不运行了,请各位自行推导程序的结果。最后在username.json文件中会存有用户的信息。但要注意一点,json根据数据类型来存储数据,虽然最后都是字符串,但这个过程不需要我们干预,比如要存一个列表,并不需要我们先将其转换为字符串,再存入json,读取数据时也不需要我们先读取为字符串,再转换成列表,我们只需直接存取即可,转换工作由json模块自动完成。

VPointer wechat
欢迎大家关注我的微信公众号"代码港"~~
您的慷慨将鼓励我继续创作~~