前言
本篇笔者将展示Python如何处理文件数据,包括文件内容的读取和写入操作,以及程序运行时异常模块的处理方式,保证我们写出健壮的代码。
一、从文件中读取数据
实际开发中我们会经常遇到操作文件的需求,读取txt文本,读取excel,生成照片,生成PDF等。想要使用文本中的信息,首先我们需要把信息读取到内存中去,读取时我们有两种处理方式,可以一次性读取全部内容到内存上,这种适合文本数据不是很大的情况,也可以一次读取一行数据进行操作,避免内存溢出。
读取整个文件
先来个小例子,首先在我们的Python的工作空间目录下创建一个 love.txt 的文本文档
接着在这个同级目录下创建一个file_reader.py的Python文件,代码如下
with open('love.txt', 'r', encoding='utf-8') as file:
content = file.read()
print(content)
这里是使用utf-8的编码方式打开一个love.txt文本,之所以使用utf-8是因为文件内容是中文,防止解析异常。接着使用read()方法读取映射的文件对象中年的内容,打印读取到的内容
文件路径
首先来看相对文件路径:相对文件路径是指相对于当前工作目录或者某个特定目录的文件路径。使用相对路径可以帮助提高代码的可移植性,因为相同的路径可以在不同的系统或环境中正确引用文件。
在python_work文件夹下创建子文件夹text_file,然后把love.txt文本文档挪进去。
file_reader.py中代码修改如下
with open('text_file/love.txt', 'r', encoding='utf-8') as file:
content = file.read()
print(content)
with open(‘text_file/love.txt’, ‘r’, encoding=‘utf-8’) as file
这行代码让Python到文件夹python_work下的文件夹text_files中去查找指定的.txt文件。
- 在Windows系统中,在文件路径中使用反斜杠()而不是斜杠(/):
with open('text_files\filename.txt') as file:
- 在Linux和OS X中,你可以这样编写代码:
with open('text_files/filename.txt') as file:
文件在计算机中的精准路径叫绝对路径,这时不用关心程序运行在计算机的什么位置,只需要告知文件的绝对路径即可读取
我们把love.txt文件挪到桌面去,右击属性找到其路径
file_reader.py的代码修改如下
with open('C:/Users/86151/Desktop/love.txt', 'r', encoding='utf-8') as file:
content = file.read()
print(content)
通过使用绝对路径,可读取系统任何地方的文件。就目前而言,最简单的做法是,要么将数据文件存储在程序文件所在的目录,要么将其存储在程序文件所在目录下的一个文件夹(如text_files)中。
注意 Windows系统有时能够正确地解读文件路径中的斜杠。如果你使用的是Windows系统,且结果不符合预期,请确保在文件路径中使用的是反斜杠。另外,由于反斜杠在Python中被视为转义标记,为在Windows中确保万无一失,应以原始字符串的方式指定路径,即在开头的单引号前加上r。
with open('C:/Users/86151/Desktop/love.txt', 'r', encoding='utf-8') as file:
content = file.read()
print(content)
上面的代码中,笔者使用斜杠也能读取,实际windows系统应该使用反斜杠
反斜杠在Python中属于转义标记,引号前加r即可,后面的 ‘r’ 其实也是为了避免读取的文件内容被转义
逐行读取
把桌面的love.txt文本内容修改如下
file_reader.py代码改为如下内容
# 打开文件并逐行读取
with open(r'C:\Users\86151\Desktop\love.txt', 'r', encoding='utf-8') as file:
for line in file:
# 处理每一行
print(line)
# 文件在此处自动关闭
可以看到读取后打印的内容和原格式不太一样,这是每行的换行符导致,打印语句做如下修改
print(line.rstrip())
再看打印内容的格式发现和原来一样了
文件内容列表
使用关键字with时,open()返回的文件对象只在with代码块内可用。如果要在with代码块外访问文件的内容,可在with代码块内将文件的各行存储在一个列表中,并在with代码块外使用该列表:你可以立即处理文件的各个部分,也可推迟到程序后面再处理。
# 打开文件并逐行读取
with open(r'C:\Users\86151\Desktop\love.txt', 'r', encoding='utf-8') as file:
lines = file.readlines()
for line in lines:
print(line.rstrip())
# 文件在此处自动关闭
方法readlines()从文件中读取每一行,并将其存储在一个列表中;接下来,该列表被存储到变量lines中;在with代码块外,我们依然可以使用这个变量。
大型文件处理
有时候我们需要的文件很大,加入一个文本中存了圆周率的值,精确到小数点后100万位。我们在读取时只要读取小数点后50位怎么办?代码示例如下
path = r'C:\Users\25541\Desktop\love.txt'
with open(path) as file:
lines = file.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(pi_string[:52] + "...")
print(len(pi_string))
二、向文件中写入数据
上一节展示的是读取一个文件中的内容,本节再来看下如何向文件中写入数据
写入空文件
先创建一个Python文件write_message.py
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
调用open()时提供了两个实参。第一个实参也是要打开的文件的名称;第二个实参(‘w’)告诉Python,我们要以写入模式打开这个文件。打开文件时,可指定读取模式(‘r’)、写入模式(‘w’)、附加模式(‘a’)或让你能够读取和写入文件的模式(‘r+’)。如果省略了模式实参,Python将以默认的只读模式打开文件。
运行这个文件会发生什么呢?如下所示
可以看到在write_message.py文件的同级目录下生成了 programming.txt 文本文档
那如果我们想把文件创建在某个文件夹下面呢?下面的代码运行会报错
filename = r'text_file\programming.txt'
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
错误是不存在这样一个文件目录,直接创建文件可以。想创建在某些文件夹路径下的文件,必须要先检查文件夹路径是否存在,不存在需要先创建,代码如下
import os
filename = r'text_file\programming.txt'
# 检查目录是否存在,如果不存在则创建
directory = os.path.dirname(filename)
if not os.path.exists(directory):
os.makedirs(directory)
# 创建文件并写入数据
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
# 读取数据
with open(filename, 'r') as file_object:
print(file_object.read())
上述代码先是导入了 os 模块,这是Python提供的基础库。接着检查了 filename 指向的相对路径中text_file是否存在,不存在则先创建一个文件夹text_file。这里要注意的是下面的代码只会创建路径中的文件夹,并不会创建最后的文件本身。
directory = os.path.dirname(filename)
if not os.path.exists(directory):
os.makedirs(directory)
创建programming.txt文件是下面的代码完成的
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
还有需要注意的是:Python只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数str()将其转换为字符串格式,否则报错
写入多行
运行如下代码
filename = 'programming.txt'
with open(filename, 'w') as file_object:
file_object.write("你好啊。")
file_object.write("吃饭了吗?")
# 读取数据
with open(filename, 'r') as file_object:
print(file_object.read())
从打印结果来看,并没有换行,这可能不是吧我们想要的结果
打开 programming.txt 文件(打开的是乱码,说明你的IDE需要设置统一下编码格式),如下显示也没有换行
想要换行很简单,每一行后面加一个 \n
file_object.write("你好啊。\n")
file_object.write("吃饭了吗?\n")
追加文件内容
上面的代码都是直接覆盖原文件,但我们也经常需要向一个文件中追加内容,也很简单只需要在open时把第二个参数换成 'a'
即可,这就是追加模式。
filename = 'programming.txt'
with open(filename, 'a') as file_object:
file_object.write("我吃过了。\n")
file_object.write("你呢?\n")
# 读取数据
with open(filename, 'r') as file_object:
print(file_object.read())
打开文件内容看到原内容并没有被覆盖
三、异常
Python中的异常使用try-except代码块处理的。try-except代码块让Python执行指定的操作,同时告诉Python发生异常时怎么办。使用了try-except代码块时,即便出现异常,程序也将继续运行:显示你编写的友好的错误消息,而不是令用户迷惑的traceback。
ZeroDivisionError异常
被除数为0的异常
print(8/0)
报错如下
Traceback (most recent call last):
File "D:\python_work\write_message.py", line 1, in <module>
print(8/0)
~^~
ZeroDivisionError: division by zero
这会导致Python程序终止运行
使用try-except代码块
try:
print(8 / 0)
except ZeroDivisionError:
print("被除数不能为0!")
这样的结构,异常就会被捕捉,不会抛出以至于终止程序运行
使用异常避免崩溃
发生错误时,如果程序还有工作没有完成,妥善地处理错误就尤其重要。这种情况经常会出现在要求用户提供输入的程序中;如果程序能够妥善地处理无效输入,就能再提示用户提供有效输入,而不至于崩溃。一个简单的除法计算器代码如下
print("Give me two numbers,and I'll divide them.")
print("Enter 'q'to quit.")
while True:
first_number = input("\nFirst number:")
if first_number == 'q':
break
second_number = input("Second number:")
if second_number == 'q':
break
answer = int(first_number) / int(second_number)
print(answer)
代码会在控制台让你依次输入两个数字,计算除法运算结果。并循环此操作,如果你的第二个操作数是0,则会抛出异常终止程序
Give me two numbers,and I'll divide them.
Enter 'q'to quit.
First number:5
Second number:2
2.5
First number:5
Second number:0
Traceback (most recent call last):
File "D:\python_work\write_message.py", line 10, in <module>
answer = int(first_number) / int(second_number)
~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
ZeroDivisionError: division by zero
else代码块
发生异常时,我们将其捕获并处理,打印出提示信息并继续循环操作
print("Give me two numbers,and I'll divide them.")
print("Enter 'q'to quit.")
while True:
first_number = input("\nFirst 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 0!")
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 0!
First number:6
Second number:7
0.8571428571428571
Python尝试执行try代码块中的代码;只有可能引发异常的代码才需要放在try语句中。有时候,有一些仅在try代码块成功执行时才需要运行的代码;这些代码应放在else代码块中。except代码块告诉Python,如果它尝试运行try代码块中的代码时引发了指定的异常,该怎么办。
处理FileNotFoundError异常
读取一个 message.txt,但是这个文件并不存在
filName = "message.txt"
with open(filName, 'w') as f:
print(f.read())
代码稍微修改下,捕获了FileNotFoundError异常
filName = "message.txt"
try:
with open(filName) as f_obj:
contents = f_obj.read()
except FileNotFoundError:
msg = "Sorry, the file " + filName + " does not exist."
print(msg)
分析文本
举个例子,比如现在要分析一个文本中的英文片段包含多少个单词
创建一个english.txt文本其中放入一段英文
Success in technology projects often depends on clear communication, thorough planning, and the ability to adapt to unexpected challenges. By focusing on these key elements, developers can enhance their chances of delivering high-quality solutions on time.
下面的代码就是统计并打印文本中有多少单词的示例,使用**split()**方法统计有多少单词
filename = 'english.txt'
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.")
分析多个文件的单词数
再创建一个team_work.txt的文本,里面放入一段英文,创建一个word_count.py的Python模块,提供一个函数统计文件的单词数
def word_count(filename):
try:
with open(filename) as f:
contents = f.read()
except FileNotFoundError:
msg = "Sorry, the file " + filename + " does not exist."
print(msg)
else:
words = contents.split()
num_words = len(words)
return num_words
把write_message.py代码修改成如下
import word_count
fileNames = ["english.txt", "team_work.txt","hello.txt"]
for fileName in fileNames:
count = word_count.word_count(fileName)
print(f"The number of words in {fileName} is {count}")
结果看到正确统计了两个文本english.txt和team_work.txt的单词数,对于不存在的hello.txt则打印出了不一样的信息
忽略失败
我们也可以让异常发生时什么也不做,只做正确执行的事,Python中有个pass关键字
word_count.py代码稍微修改下,把打印统计信息直接放在这个函数里了
def word_count(filename):
try:
with open(filename) as f:
contents = f.read()
except FileNotFoundError:
pass
else:
words = contents.split()
num_words = len(words)
print(f"The number of words in {filename} is {num_words}")
write_message.py也稍微修改下
import word_count
fileNames = ["english.txt", "team_work.txt", "hello.txt"]
for fileName in fileNames:
word_count.word_count(fileName)
再此执行 write_message.py的代码,看到hello.txt文件不存在的异常被pass了
四、存储数据
程序本身就是操作各种数据的,算法也是围绕着数据来的,考虑如何提升数据的操作效率等等。在Python中一般使用列表或者字典来临时接受用户提供的信息,在用户退出或者下达保存指令时再持久化数据到磁盘中。下面介绍一种使用json格式保存数据的方法,注意json格式数据不是Python独有。
使用json.dump()和json.load()
json.dump()
- 包含两个参数,要存储的数据以及可用于存储数据的文件对象
下面代码演示了如何使用json.dump()来存储数字列表:
import json
numbers = [2,3,5,7,11,13]
filename = 'numbers.json'
with open(filename,'w') as f_obj:
json.dump(numbers,f_obj)
先导入模块json,再创建一个数字列表,接着创建一个numbers.json的文件,向其中写入数字列表
json.load()
创建一个numbers_reader.py模块
import json
filename = "numbers.json"
with open(filename, 'r') as f:
numbers = json.load(f)
print(numbers)
这段代码读取了之前创建的numbers.json文件,使用了json.load()方法
保存和读取用户输入的用户名
下面的代码完成了提示用户在控制台输入用户名,并已json形式存储到文件中的功能
import json
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
username = input("What is your name? ")
with open(filename, 'w') as f:
json.dump(username, f)
else:
print("Welcome back, " + username + "!")
运行后会在同级目录下生成numbers.json文件
那如果把生成的numbers.json文件中的 "jack"删除掉,再次运行代码会发生什么?
直接抛出json解析异常 json.decoder.JSONDecodeError
我们需要捕获这个异常避免报错
import json
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
username = input("What is your name? ")
with open(filename, 'w') as f:
json.dump(username, f)
except json.decoder.JSONDecodeError:
username = input("What is your name? ")
with open(filename, 'w') as f:
json.dump(username, f)
else:
print("Welcome back, " + username + "!")
这样当json文件存在,但是没有数据时,也不会抛出json解析异常了
代码重构
上面的代码相对不成体系,结构混乱,职责划分不清晰,下面的代码巧妙使用了一个个小函数,完成了简单的记录用户名,问候用户的功能
import json
def get_stored_username():
"""如果存储了用户名,就获取它"""
filename = 'username.json'
try:
with open(filename) as f_obj:
username = json.load(f_obj)
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_obj:
json.dump(username,f_obj)
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()
代码能够正确地运行,但可做进一步的改进——将代码划分为一系列完成具体工作的函数。这样的过程被称为重构。重构让代码更清晰、更易于理解、更容易扩展。我们不要总是偷懒,代码是需要不断优化调整的。
总结
本章主要介绍了文件的处理方式以及异常的捕获和处理,我们离真正的代码实战越来越近了,内容虽然简单,但是任然需要我们勤加练习才能掌握。