Python 文件操作与异常处理
本文介绍了Python文件操作和异常处理的核心知识。在文件操作部分,详细讲解了open()函数的使用方法、文件读写模式(如'r'、'w'、'a'等)、with语句的安全用法、文件对象常用方法(read/write等)以及二进制文件处理。同时介绍了os和pathlib模块进行目录操作和文件管理。在异常处理部分,阐述了常见异常类型(如ValueError、ZeroDivisionError等)及其处理
一 文件操作
1.1 文件的基本操作
1.1.1 创建和打开文件
在 Python 中,想要操作文件需要先创建或者打开已存在的文件并创建文件对象,可以通过内置的 open() 函数实现。
open() 函数的基本语法格式如下:
file = open(file_name, mode)
-
file_name:是文件的名称(包括路径)。
-
mode:是打开文件的模式,可选参数,它默认为 'r' 表示以文本模式读取。
-
file:返回值,是一个文件对象。
mode 参数的参数值说明,如下表所示:
参数值 |
描述 |
'r' |
读取模式(默认)。 |
'w' |
写入模式,如果文件存在则覆盖,不存在则创建。 |
'a' |
追加模式,如果文件存在则在文件末尾添加内容,不存在则创建。 |
'x' |
独占创建模式,如果文件已存在则失败。 |
'b' |
二进制模式。 |
't' |
文本模式(默认)。 |
'+' |
读写模式。 |
上表中的模式可以组合使用,比如:
-
'r+':读写模式,文件必须存在。
-
'w+':读写模式,文件存在则覆盖,不存在则创建。
-
'a+':读写模式,文件存在会在末尾追加内容,不存在则创建。
-
'rb':读取二进制文件,比如图片、视频、音频文件等。
-
'wb':写入二进制文件,文件存在则覆盖,不存在则创建。
1.1.2 操作文件
使用 open() 函数打开文件,它返回一个文件对象,然后可以使用该文件对象进行读取或写入操作。
操作文件的经典三部曲是:打开文件、读写文件、关闭文件,代码如下:
# 文件操作
# 经典三部曲(不推荐)
f = open('demo.txt', 'w', encoding='utf-8') # 打开文件
f.write('Hello World') # 读取或写入文件
f.close() # 关闭文件
在 Python 中上面这种写法不推荐,我们推荐使用 with 语句来自动管理文件的打开和关闭,即使在读取或写入时发生异常也能确保文件正确关闭,代码如下:
# 文件操作
# 现代安全写法(推荐)使用with语句打开文件
with open('demo.txt', 'r') as file:
content = file.read()
print(content)
# 文件在这里会自动关闭
输出结果:
Hello World
文件对象的常用方法如下:
-
read(size=-1):读取整个文件或指定大小的数据。
-
readline():读取一行。
-
readlines():读取所有行并返回一个列表,每行作为列表的一个元素。
-
write(string):将字符串写入文件。
-
writelines(lines):将一个字符串列表写入文件。
-
seek(offset, whence=0):改变当前文件操作指针的位置。
-
tell():返回当前文件操作指针的位置。
-
flush():刷新文件内部缓冲区。
-
close():关闭文件。
我们以具体的示例来看下如何通过文件对象的这些方法来操作文件:
(1)写入文件
要写入文件,可以使用 open 函数,并以写入模式('w')打开文件。如果你想要追加内容到文件,可以使用追加模式('a')。
示例1,写入文件,代码如下:
# 文件操作
# 写入模式,如果文件不存在会自动创建
with open('test.txt', 'w') as file:
file.write("Hello, world!\n")
file.write("这是第二行。\n")
运行后,test.txt 文件的内容如下:
Hello, world!
这是第二行。
还可以使用 writelines() 函数,将字符串列表的内容写入文件,代码如下:
# 使用 writelines 方法将字符串列表写入文件
lines = ['First line\n', 'Second line\n', 'Third line\n']
with open('example.txt', 'w') as file:
file.writelines(lines)
在上面的示例中,我们将字符串列表中的多个字符串写入文件。在字符串的尾部添加了“\n”,写入换行符。
示例2,追加内容到文件,代码如下:
# 文件操作
# 追加模式,如果文件不存在会自动创建
with open('test.txt', 'a') as file:
file.write("这是追加的内容。\n")
运行后,test.txt 文件的内容如下:
Hello, world!
这是第二行。
这是追加的内容。
(2)读取文件
要读取文件,你可以使用 open 函数,并以读取模式('r')打开文件。你可以使用 read() 方法读取整个文件或指定大小的数据,也可以使用 readline() 方法逐行读取,或者使用 readlines() 方法读取所有行到一个列表中。
示例1,读取整个文件,代码如下:
# 文件操作
with open('test.txt', 'r') as file:
content = file.read() # 读取整个文件
print(content)
输出结果:
Hello, world!
这是第二行。
这是追加的内容。
示例2,读取特定数量的字符(读取前5个字符),代码如下:
# 文件操作
with open('test.txt', 'r') as file:
content = file.read(5) # 读取前5个字符
print(content)
输出结果:
Hello
示例3,逐行读取文件,有两种方式:使用 for 循环和使用 readline() 函数,代码如下:
# 文件操作
# 使用 for 循环读取每一行,并显示
with open('test.txt', 'r') as file:
for line in file:
print(line.strip()) # 使用strip()移除行尾的换行符
# 文件操作
# 使用 readline 读取每一行,并显示
with open('test.txt', 'r') as file:
while True:
line = file.readline() # 使用readline读取一行
if not line:
break
print(line.strip()) # 使用 strip 移除行尾的换行符
以上两种方式,输出结果都一样:
Hello, world!
这是第二行。
这是追加的内容。
示例4,读取所有行到一个列表中,使用 readlines() 函数,代码如下:
# 文件操作
# 读取每一行并处理
with open('test.txt', 'r') as file:
lines = file.readlines()
for line in lines:
print(line.strip()) # 使用strip()移除行尾的换行符
读取文件注意事项:
在读取大文件时,直接使用 Python 的 read() 或 readlines() 方法读取所有内容,可能会消耗大量内存,因为它会一次性将文件的全部内容加载到内存中。对于非常大的文件,更好的做法是逐行读取文件,这样可以显著减少内存的使用。
(3)二进制文件的读写
对于非文本文件,比如,图片、音频或视频文件,我们需要以二进制模式读写文件。
对于二进制文件的操作,只需在打开文件时添加'b'模式。例如,读取二进制文件,以图片为例,代码如下:
with open('python-logo.png', 'rb') as file:
content = file.read() # 读取二进制数据到内存中,例如图片数据。
写入二进制数据,代码如下:
with open('image_copy.png', 'wb') as file:
file.write(content) # 写入二进制数据,例如图片数据。确保content是二进制格式。
(4)文件指针(位置)操作
文件指针(File Pointer)的概念在文件操作中非常重要,尤其是在需要精确控制文件读写位置时。文件指针通常指的是当前操作的文件位置(即读写位置的索引),它可以帮助我们定位到文件的特定部分进行操作。
当你打开一个文件进行读取或写入时,Python 会自动管理文件指针的位置。对于文本文件,每次读取或写入操作后,文件指针会自动移动到下一个字符或行。对于二进制文件,每次操作后,文件指针会根据读取或写入的数据量相应移动。
可以使用 seek() 方法来移动文件指针到文件的特定位置。例如:
with open('test.txt', 'r') as file:
file.seek(10) # 将文件指针移动到第11个字节的位置(因为计数从0开始)
使用 tell() 方法可以获取当前文件指针的位置:
position = file.tell() # 获取当前文件指针的位置
示例1,读取内容后文件指针位置,代码如下:
# 文件指针
with open('test.txt', 'r') as file:
content = file.read(10) # 读取前10个字符
print(content)
position = file.tell() # 获取当前位置
print(f'当前位置: {position}') # 输出当前位置
输出结果:
Hello, wor
当前位置: 10
示例2,写入内容后文件指针位置,代码如下:
# 文件指针
with open('test.txt', 'a') as file: # 打开文件用于追加
file.write('Hello, Python!') # 写入字符串
position = file.tell() # 获取当前位置(注意:对于文本模式,tell()返回的是下一个字符的索引,而非最后一个字符的索引)
print(f'当前位置: {position}') # 输出当前位置
输出结果:
当前位置: 61
代码执行后,文件内容变为:
Hello, world!
这是第二行。
这是追加的内容。
Hello, Python!
1.1.3 文件编码
在 Python 中处理文本文件时,正确地处理编码是非常重要的,尤其是当你需要读写非 ASCII 字符(如中文等)的文件时。
Python 提供的 open 函数用来打开文件时可以指定编码方式(可选参数encoding),常用编码:
-
UTF-8:广泛使用的编码,支持大多数语言的字符。
-
ASCII:仅支持英文字符。
-
ISO-8859-1:西欧语言的标准编码。
-
GBK:用于简体中文。
-
GB2312:早期简体中文标准。
-
UTF-16:Unicode的一种实现方式,使用两个字节表示一个字符。
读取文件时,可以使用 open 函数的 encoding 参数来指定编码方式。例如,读取一个 UTF-8 编码的文本文件:
with open('encode.txt', 'r', encoding='utf-8') as file:
content = file.read()
print(content)
写入文件时,同样可以指定编码。例如,将内容写入一个 UTF-8 编码的文本文件:
with open('encode.txt', 'w', encoding='utf-8') as file:
file.write("这是一些文本。")
编码(encoding)决定了文件内容的字符如何被解释和转换成字节。如果在读取含中文的文本文件时出现乱码,多半是因为编码的问题,比如 gbk 编码写入的内容,用 utf-8 读取,就会出错,读取和写入要使用相同的编码规则。
注意:对于二进制的文件操作则不需要指定编码。
1.1.4 文件删除和重命名
文件删除和重命名可以使用 os 或 pathlib 模块中的函数来实现,代码如下:
import os
from pathlib import Path # 同时可以使用pathlib进行删除和重命名操作,但需要转换为str或使用Path对象的方法。
# 删除文件(使用os模块)
os.remove('example.txt') # 或者使用 Path('example.txt').unlink()(pathlib)
# 重命名文件(使用os模块)
os.rename('oldname.txt', 'newname.txt') # 或者使用 Path('oldname.txt').rename('newname.txt')(pathlib)
1.2 目录的操作
在 Python 中,对目录(文件夹)的操作是非常常见的任务。Python 提供了几个内置的模块来帮助你执行这些操作,其中最常用的是 os 模块和 pathlib 模块。下面会分别介绍如何使用这两个模块来进行目录的操作。
1.2.1 使用 os 模块
在 Python 中,os模块提供了许多用于处理文件和目录路径的函数。这些函数可以帮助你执行诸如创建目录、删除目录、更改当前工作目录、列出目录内容等操作。以下是一些与目录相关的常用os模块函数:
(1)获取当前工作目录
os.getcwd():返回当前工作目录的路径。
(2)更改当前工作目录
os.chdir(path):更改当前工作目录到指定的路径。
(3)创建目录
-
os.mkdir(path):创建一个目录。如果上级目录不存在,则会抛出FileNotFoundError。如果要创建的目录已经存在,则会抛出FileExistsError。
-
os.makedirs(path):创建一个多级目录,如果上级目录不存在,会自动创建上级目录。
(4)删除目录
-
os.rmdir(path):删除一个空目录。如果目录不为空,则会抛出OSError。
-
os.removedirs(path):删除一个目录,如果该目录为空且所有上级目录也都是空的,则一并删除它们。如果目录不为空,则会抛出OSError。os.removedirs() 更适合用于删除嵌套的空目录结构。
(5)列出目录内容
-
os.listdir(path='.'):返回指定路径下的文件和目录名列表。如果不指定路径,默认为当前工作目录。
-
os.scandir(path='.'):返回一个迭代器,迭代器中的每个元素都是一个os.DirEntry对象,提供了比os.listdir()更详细的信息(例如文件类型)。
(6)检查路径是否存在
-
os.path.exists(path):检查路径是否存在。
-
os.path.isdir(path):检查路径是否存在且是否为目录。
-
os.path.isfile(path):检查路径是否存在且是否为文件。
(7)路径操作
-
os.path.join(path, *paths):将多个路径组件合并成一个路径。
-
os.path.abspath(path):返回路径的绝对版本。
-
os.path.basename(path):返回路径的基本名称(最后一部分)。
-
os.path.dirname(path):返回路径的目录名称(最后一部分之前的部分)。
-
os.path.split(path):将路径分割成目录名和文件名元组。
-
os.path.splitext(path):将路径分割成文件名和扩展名元组。
我们通过示例代码来看下这些函数的具体用法,代码如下:
# 使用 os 模块中的函数操作目录
import os # 导入os模块
# 获取当前工作目录
print("当前工作目录:", os.getcwd())
# 更改当前工作目录(确保路径存在)
# os.chdir('E:/PythonCode1')
# 创建目录
os.mkdir('new_directory1')
os.makedirs('new_directory2/sub_directory/dir1')
os.mkdir('new_directory3')
# 列出当前工作目录内容
print("当前目录内容:", os.listdir())
# 检查路径是否存在及类型
print("检查路径:", os.path.exists('new_directory2/sub_directory/dir1'))
print("是目录吗?", os.path.isdir('new_directory2/sub_directory'))
print("是文件吗?", os.path.isfile('new_directory3'))
# 合并路径
print("合并路径:", os.path.join('dir1', 'dir2', 'filename.txt'))
# 删除目录
os.rmdir('new_directory3')
# 列出当前工作目录内容
print("当前目录内容:", os.listdir())
输出结果:
当前工作目录: E:\PythonCode
当前目录内容: ['demo16.py', 'new_directory1', 'new_directory2', 'new_directory3', 'utils.py', '__pycache__']
检查路径: True
是目录吗? True
是文件吗? False
合并路径: dir1\dir2\filename.txt
当前目录内容: ['demo16.py', 'new_directory1', 'new_directory2', 'utils.py', '__pycache__']
这些函数提供了强大的工具来处理文件和目录,使得Python脚本能够有效地管理文件系统中的对象。
1.2.2 使用 pathlib 模块
在 Python 中,pathlib 模块提供了一个面向对象的文件系统路径操作方法,它旨在替代旧的 os.path 模块。pathlib 模块提供了许多用于处理文件和目录的便捷方法。以下是一些与目录相关的常用函数和方法:
(1)创建目录
Path.mkdir(mode=0o777, parents=False, exist_ok=False)
-
parents 参数如果为True,则会创建所有必需的父目录。
-
exist_ok 参数如果为True,则如果目录已存在,不会抛出异常。
代码示例如下:
# 使用 pathlib 模块中的函数操作目录
from pathlib import Path # 导入pathlib模块
# 创建一级目录
dir_path1 = Path('new_directory1')
dir_path1.mkdir(parents=True, exist_ok=True)
# 创建多级目录
dir_path2 = Path('new_directory2/sub1/sub2')
dir_path2.mkdir(parents=True, exist_ok=True)
(2)列出目录内容
-
Path.iterdir():返回目录中的文件和子目录的迭代器。
-
Path.glob(pattern):根据模式查找文件或目录。
-
Path.rglob(pattern):递归地查找匹配模式的所有文件或目录。
代码示例如下:
# 使用 pathlib 模块中的函数操作目录
from pathlib import Path # 导入pathlib模块
dir_path = Path('new_directory1')
print('new_directory1目录下的所有内容:')
for entry in dir_path.iterdir():
print(entry)
# 使用glob查找所有.txt文件
print('\nnew_directory1目录下的所有txt文件:')
for txt_file in dir_path.glob('*.txt'):
print(txt_file)
输出结果:
new_directory1目录下的所有内容:
new_directory1\1.txt
new_directory1\ReadME.txt
new_directory1\统计报表.xlsx
new_directory1目录下的所有txt文件:
new_directory1\1.txt
new_directory1\ReadME.txt
(3)检查路径是否存在
-
Path.exists():检查路径是否存在。
-
Path.is_dir():检查路径是否为目录。
-
Path.is_file():检查路径是否为文件。
代码示例如下:
# 使用 pathlib 模块中的函数操作目录
from pathlib import Path # 导入pathlib模块
dir_path = Path('new_directory1')
if dir_path.exists() and dir_path.is_dir():
print("Directory exists.")
(4)删除目录
-
Path.rmdir():删除空目录。如果目录非空,将引发异常。
-
Path.unlink(missing_ok=False):删除文件或符号链接。如果要删除目录,需要先删除其内容或将missing_ok设置为True。
代码示例如下:
# 使用 pathlib 模块中的函数操作目录
from pathlib import Path # 导入pathlib模块
dir_path = Path('new_directory2/sub1/sub2')
dir_path.rmdir()
执行以上代码后,如果 sub2 目录为空,则被删除,否则引发异常。
(5)重命名/移动目录
-
Path.rename(target):重命名文件或目录,也可以用于移动文件或目录到不同位置。
-
Path.replace(target):重命名文件或目录,如果目标已存在,则替换它。
代码示例如下:
# 使用 pathlib 模块中的函数操作目录
from pathlib import Path # 导入pathlib模块
src = Path('new_directory2')
dst = Path('new_directory3')
src.rename(dst) # 这会重命名或移动目录,取决于目标路径。
以上代码中,new_directory2 目录被重命名为 new_directory3。
(6)获取绝对路径和当前工作目录
-
Path.absolute():获取绝对路径。
-
Path.cwd():获取当前工作目录的Path对象。等价于Path('.')或Path('./')。
Path() 构造函数可以直接从当前工作目录开始构建路径。例如,Path('subdir')等同于当前工作目录下的subdir。
代码示例如下:
# 使用 pathlib 模块中的函数操作目录
from pathlib import Path # 导入pathlib模块
current_dir = Path.cwd() # 获取当前工作目录的Path对象
print(current_dir) # 打印当前工作目录的绝对路径或相对路径(取决于如何调用)
relative_path = Path('new_directory1/1.txt')
absolute_path = relative_path.absolute()
print('new_directory1/1.txt 的绝对路径为:', absolute_path) # 输出绝对路径
输出结果:
E:\PythonCode
new_directory1/1.txt 的绝对路径为: E:\PythonCode\new_directory1\1.txt
二 异常处理
2.1 什么是异常
异常是指在程序运行过程中发生的错误或意外情况,这些情况会导致程序无法按照预期的方式继续执行。
异常是 Python 中的一个对象,表示一个错误。一般情况下,在 Python 无法正常处理程序时就会发生一个异常,例如正在进行网络文件传输,突然断网了,这就是个异常,是个意外情况,因此,异常是不可避免的。当发生异常时需要捕获并处理它,否则程序会终止执行。
2.2 常见的异常
Python 提供了强大的异常处理机制,对程序在执行过程中出现的异常正确捕获并处理后,可以使程序继续执行下去。
Python 中常见的异常如下表所示:
异常名称 |
描述 |
ZeroDivisionError |
除以 0 时引发。 |
FileNotFoundError |
尝试打开一个不存在的文件时引发。 |
TypeError |
类型不匹配时引发。 |
KeyError |
尝试访问字典中不存在的键引发。 |
IndexError |
索引超出序列的范围引发。 |
NameError |
尝试访问一个尚未被定义的名字引发。 |
ValueError |
传递给函数的参数值不正确引发。 |
SyntaxError |
语法错误。 |
IndentationError |
缩进错误。 |
在 Python 编程中,经常会遇到各种异常。除了以上表格中的几种外,Python 还提供了很多内置异常,遇到时可通过查询官方文档处理,了解这些异常并知道如何处理它们是提高代码质量和稳定性的关键。
2.3 处理异常
开发过程中,遇到异常我们应该如何处理呢?
在 Python 中,异常处理是通过 try、except、else 和 finally 这几个关键字来实现的。这种机制允许你在程序执行过程中捕获并处理错误,从而避免程序因为一个未处理的异常而完全崩溃。
异常处理语句的基本语法如下:
try:
# 尝试执行的代码块
可能引发异常的代码
except SomeException:
# 如果try块中的代码引发了SomeException异常,则执行这里的代码
处理异常的代码
else:
# 如果try块中的代码没有引发异常,则执行这里的代码
没有异常时的代码
finally:
# 无论是否发生异常,这里的代码都会被执行
总是要执行的代码
(1)基本用法
下面我们以实际代码,来看下 try-except 语句的用法,代码如下:
try:
num = int(input("请输入整数:"))
result = 10 / num
except ValueError:
print("输入的不是整数!")
except ZeroDivisionError:
print("不能除以0!")
else:
print("结果是:", result) # 无异常时执行
finally:
print("程序执行完毕。") # 无论是否异常都会执行
当我们输入整数 3 时,没有发生异常,则执行 else 语句中的代码,输出结果:
请输入整数:3
结果是: 3.3333333333333335
程序执行完毕。
当我们输入字母 a 时,由于 a 不是整数,引发了 ValueError 异常,则执行 except ValueError: 中的代码,输出结果:
输入整数:a
输入的不是整数!
程序执行完毕。
当我们输入数字 0 时,引发了 ZeroDivisionError 异常,则执行 except ZeroDivisionError: 中的代码,输出结果:
输入整数:0
不能除以0!
程序执行完毕。
从以上代码,我们看出,当执行 try 子句中的代码,如果出现异常,我们可以使用 except 子句来捕获并处理具体的异常;如果没有异常,则执行 else 子句;无论是否发生异常,finally 子句都会执行。
几点注意事项:
-
在使用 except 捕获异常时,尽量捕获具体的异常类型而不是使用 Exception,除非你确定需要捕获所有类型的异常。这有助于保持代码的清晰性和维护性。
-
finally 块用于执行清理工作,例如关闭文件、网络连接等,无论是否发生异常,finally 块中的代码都会被执行。
-
使用 else 块可以在没有异常发生时执行特定的代码。这在某些情况下非常有用,比如当你需要验证某些操作是否成功完成时。
(2)捕获所有异常
如果你想捕获所有类型的异常,可以使用 Exception 类。但是,通常不推荐这样做,因为它会捕获所有异常,包括编程错误(如语法错误),这可能会掩盖真正的 bug。
示例代码如下:
try:
result = 10 / 0
except Exception as e: # 捕获所有异常类型
print("发生了一个异常: ", e)
输出结果:
发生了一个异常: division by zero
(3)使用 raise 关键字重新抛出异常
在 Python 中,可以使用 raise 关键字来手动抛出异常。例如,你可以在某个条件不满足时抛出一个自定义的异常。例如:raise ValueError("无效的值")。
raise 的语法格式如下:
raise [ExceptionName[(reason)]]
其中,ExceptionName[(reason)] 为可选参数,用于指定抛出的异常名称以及异常信息的相关描述。如果省略,就会把当前的错误原样抛出。
说明:ExceptionName(reason) 参数中的“(reason)”也可以省略,如果省略,则在抛出异常时,不附带任何描述信息。
示例代码如下:
def check_age(age):
if age < 0:
raise ValueError("年龄不能为负数!")
return age
try:
check_age(-5)
except ValueError as e:
print(e) # 输出:年龄不能为负数!
输出结果:
年龄不能为负数!
以上代码中,check_age 函数检测 age 小于 0 时,会手动抛出一个 ValueError 异常,异常的描述信息是:"年龄不能为负数!"。
如果你觉得文章还不错,请大家 点赞、分享、留言 下,因为这将是我持续输出更多优质文章的最强动力!
最后这里免费分享给大家一份Python全套学习资料,包含视频、源码。课件,希望能帮到那些不满现状,想提升自己却又没有方向的朋友,也可以和我一起来学习交流呀。
编程资料、学习路线图、源代码、软件安装包等!【点击这里】领取!
① Python所有方向的学习路线图,清楚各个方向要学什么东西
② 100多节Python课程视频,涵盖必备基础、爬虫和数据分析
③ 100多个Python实战案例,学习不再是只会理论
④ 华为出品独家Python漫画教程,手机也能学习
⑤ 历年互联网企业Python面试真题,复习时非常方便
更多推荐
所有评论(0)