第八章 Python文件操作
8.1. os模块
8.1.1. os模块介绍
os是OperateSystem的简称,即操作系统。使用os模块,可以实现对操作系统的文件系统进行简单的操作。
在使用之前,需要先导入模块
import os
# 获取当前系统名称
print(os.name)
8.1.2. 环境变量相关
# 获取系统中所有的环境变量
print(os.environ)
# 获取指定的环境变量的值
print(os.environ.get("PATH"))
print(os.getenv("PATH"))
8.1.3. 路径相关
# 获取当前目录的相对路径表示法
print(os.curdir) # .
# 获取当前目录的绝对路径
print(os.getcwd())
8.1.4. 文件夹操作相关
# 创建文件夹,如果目标已存在,会出异常FileExistsError
# 只支持创建一层文件夹,不支持多级文件夹的创建
os.mkdir("./test")
# 创建多级文件夹
os.mkdir
# 创建多级文件夹,如果目标已存在,会出异常FileExistsError
os.makedirs('./demo/subdemo')
# 移除文件夹,只能移除空文件夹,删除非空的文件夹会出异常
os.rmdir("./demo")
# 移除多级文件夹,只能删除空的文件夹
os.removedirs("./demo/subdemo")
# 移除文件
os.remove("./demo/subdemo")
# 重命名文件/文件夹,可以实现移动的效果
os.rename("./test", "./test2")
8.1.5. 子文件操作
# 获取指定的文件夹下,直接包含的子文件名和子文件夹的名
print(os.listdir("./"))
8.2. os.path模块
8.2.1. os.path模块的介绍
os.path其实是os模块的子模块,其实就是个子包。
在os模块中,我们可以对文件夹进行若干的操作,而os.path模块中包含的更多都是文件、文件夹属性获取的操作。
import os
8.2.2. 路径判断
# 1. 判断路径是否是真实存在的
print(os.path.exists('./test'))
print(os.path.exists('../test'))
# 2. 判断路径表达的是否为文件 [检验的后缀的情况]
print(os.path.isfile('./test/hello.txt'))
print(os.path.isfile('./test/hello'))
# 3. 判断路径表达的是否为文件夹
print(os.path.isdir('./test/hello.txt'))
print(os.path.isdir('./test')) # True
# 4. 判断路径表达的是否为绝对路径
print(os.path.isabs('./test')) # absolute False
print(os.path.isabs(os.getcwd())) # True
# 5. 获取相对路径对应的绝对路径
print(os.path.abspath('./test'))
# 6. 获取路径中的最后一级路径名
path = r'C:\Users\shawn\Documents\上课资料\语法基础\day18\代码\BaseProject\day18\test\hello.txt'
print(os.path.basename(path))
# 7. 获取路径中包含最后一个的文件夹路径
print(os.path.dirname(path))
# C:\Users\shawn\Documents\上课资料\语法基础\day18\代码\BaseProject\day18\test
# 8. 获取文件的后缀名 结果是一个二元组 如果是文件 第二个元素就是后缀名 文件夹的话就是空字符串【按照后缀切割 路径, 后缀名】
print(os.path.splitext(path))
# ('C:\\Users\\shawn\\Documents\\上课资料\\语法基础\\day18\\代码\\BaseProject\\day18\\test\\hello', '.txt')
print(os.path.splitext(r'C:\Users\shawn\Documents\上课资料\语法基础\day18\代码\BaseProject\day18\test'))
# ('C:\\Users\\shawn\\Documents\\上课资料\\语法基础\\day18\\代码\\BaseProject\\day18\\test', '')
# 9. 路径拼接
super_path = r'C:\\Users\\shawn\\Documents\\上课资料\\语法基础\\day18\\代码\\BaseProject\\day18\\test'
print(os.path.join(super_path, 'hello.txt'))
# C:\\Users\\shawn\\Documents\\上课资料\\语法基础\\day18\\代码\\BaseProject\\day18\\test\hello.txt
8.2.3. 文件属性获取
# 1. 获取文件的字节大小
print(os.path.getsize('./1.OS模块.py'))
# 2. 获取文件的创建时间
print(os.path.getctime('./1.OS模块.py'))
import datetime
print(datetime.datetime.fromtimestamp(1661916953.9453664))
# 3. 获取文件的修改时间
print(os.path.getmtime('./1.OS模块.py'))
print(datetime.datetime.fromtimestamp(1661917233.2336862))
# 4. 获取文件的访问时间
print(os.path.getatime('./1.OS模块.py'))
print(datetime.datetime.fromtimestamp(1661930617.1276166))
8.2.4. 练习
获取指定目录下的所有的文件
import os
def get_all_file(dir_path):
file_names = os.listdir(dir_path)
for fn in file_names:
file_path = os.path.join(dir_path, fn)
if os.path.isfile(file_path):
print(file_path)
elif os.path.isdir(file_path):
get_all_file(file_path)
get_all_file('./test')
'''
./test
|- demo hello.txt
|- ./test/demo
|- 1.txt dir
|- 2.txt
'''
import os
def print_all_files(path, level):
"""
需求:获取一个路径下的所有的子文件
:param path: 需要查询文件的路径
:return:
"""
prefix = "|" + "----|" * level
print(prefix, end=os.path.basename(path))
print()
# 1. 获取到所有的子文件
sub = os.listdir(path)
# 2. 遍历每一个文件
for f in sub:
# 3. 拼接路径
full_path = os.path.join(path, f)
# 4. 判断
if os.path.isfile(full_path):
print(prefix, end=f)
print()
else:
print_all_files(full_path, level + 1)
print_all_files("/Users/shawn/PycharmProjects/pythonProject", 0)
删除指定的文件夹
import os
def remove_path(dir_path):
file_names = os.listdir(dir_path)
for fn in file_names:
file_path = os.path.join(dir_path, fn)
if os.path.isfile(file_path):
os.remove(file_path)
elif os.path.isdir(file_path):
remove_path(file_path)
os.rmdir(dir_path)
remove_path('./test1')
8.3. 读写文件
使用**open()**方法打开一个文件,建立一个程序与文件的数据流。在打开文件的时候需要设置打开的模式,常见的打开模式如下:
打开模式 | 描述 | 文件指针 | 异常 |
---|---|---|---|
r | 只读模式打开,默认 | 开头 | 如果文件不存在,会出现异常 |
r+ | 读写模式打开 | 开头 | 如果文件不存在,会出现异常 |
w | 只写模式打开,打开文件会清空原有内容 | 开头/结尾 | |
w+ | 读写模式打开,打开文件会清空原有内容 | 开头/结尾 | |
a | 只写模式打开 | 结尾 | |
a+ | 读写模式打开 | 结尾 | |
b | 二进制数据流,可以与上述的混合使用 |
8.3.1. 读取文件的基本操作
from collections.abc import Iterator, Iterable
# 读取文件中的内容
# 打开指定的文件,设置打开模式为r,表示读取;设置encoding字符集,使用utf8字符集打开文件;获取到一个数据流
read_stream = open("./1. OS模块.py", "r", encoding="utf8")
# 打开文件后,建立数据通道,每一行数据就已经存在于一个迭代器容器中。
print(isinstance(read_stream, Iterator))
print(isinstance(read_stream, Iterable))
# 遍历,获取到文件中的每一行数据
# for line_data in read_stream:
# print(line_data, end="")
# 还可以将文件中的数据当作一个字符串全部读取
# file_data = read_stream.read()
# print(file_data)
# 读取指定数量的字符
# data = read_stream.read(30)
# print(data)
# 读取一行数据
print(read_stream.readline(), end="")
print(read_stream.readline(), end="")
print(read_stream.readline(), end="")
print(read_stream.readline(), end="")
# 读取指定行的数据
# 参数传递的是一个字符的数量,返回的是这些字符所在的行数据!
print(read_stream.readlines(34), end="")
read_stream.close()
8.3.2. 循环读取文件
'''
内存是2G
文件内容是4G
内容是迭代器 读出来之后迭代器中就没有了
分批读取 肯定是将读的操作进行重复 重复到什么情况下不用再重复了 [read的内容是空字符串]
分批读取 怎么分批 一般是按照计算单位的换算来走的 通常是1024的倍数
'''
read_stream = open('./os的操作_08.py', 'r', encoding='utf-8')
while True:
data = read_stream.read(1024)
print(data)
if data == '':
break
8.3.3. 以字节模式读取
read_stream = open('./os的操作_08.py', 'rb')
# for ele in read_stream:
# print(ele)
# data = read_stream.read(18) # 字节数
# print(data)
# print(data.decode('utf-8'))
print(read_stream.readlines())
read_stream.close()
# import os
# os.remove('./os的操作_08.py')
8.3.4. 写模式的基本操作
# 和文件之间建立通道
# w: 写模式
# a: 向后追加,如果没有设置追加模式,则默认会用新的内容覆盖掉之前的内容
write_stream = open("./相思.txt", "w", encoding="utf8")
# 写操作,一次写
write_stream.write("红豆生南国,春来发几支?\n")
# 同时写多个
write_stream.writelines(["愿君多采撷, ", "此物最相思\n"])
write_stream.close()
8.3.4. 以字节模式写入
# 和文件之间建立通道
# w: 写模式
# a: 向后追加,如果没有设置追加模式,则默认会用新的内容覆盖掉之前的内容
# b: binary,字节模式
write_stream = open("./相思.txt", "wb")
# 写操作,一次写
write_stream.write("红豆生南国,春来发几支?\n".encode("utf8"))
# 同时写多个
write_stream.writelines(map(lambda ele: ele.encode("utf8"), ["愿君多采撷, ", "此物最相思\n"]))
write_stream.close()
8.4. 拷贝文件
# 其实拷贝文件的思路非常简单,就是读取源文件,将读取到的内容原封不动的写入到目标文件即可
def copy_file(src_file, dst_file):
"""
拷贝文件
:param src_file: 源文件
:param dst_file: 目标文件
:return:
"""
# 1. 以字节模式打开源文件,创建数据流
read_stream = open(src_file, "rb")
# 2. 以字节模式打开目标文件,创建数据流
write_stream = open(dst_file, "wb")
# 3. 循环读取源文件的内容,往目标文件写
while len(read_data := read_stream.read(1024)) != 0:
write_stream.write(read_data)
write_stream.flush()
copy_file("./1. OS模块.py", "./1. OS模块.py.copy")
8.5. with语句
8.5.1. 为什么要使用with
无论是在读取文件,还是在写文件的时候,我们需要先使用open
语句,建立一个程序与文件的连接,获取到数据流。然后对文件进行读、写的操作。操作之后需要将这个连接断掉,释放资源。
# 1. 建立程序与文件的连接
read_data = open("./test.py", "r", encoding="utf8")
# 2. 读取数据
read_data.readline()
# 3. 释放资源
read_data.close()
在实际的操作过程中,有可能会出现资源没有释放,大概两种情况会导致:
- 自己忘了写close了
- 读取数据过程中出现异常,导致后面的close没有执行
第一个我们可以通过更加细心来解决,而第二个我们可以通过try语句来解决:
# 1. 建立程序与文件的连接
read_data = open("./test.py", "r", encoding="utf8")
try:
# 2. 读取数据
read_data.readline()
finally:
# 3. 释放资源
read_data.close()
问题的确解决了,但是这样的代码看着就非常麻烦了。那么能不能简化这个过程呢?with就出现了,我们可以将上述的语句修改如下:
with open("./test.py", "r", encoding="utf8") as read_data:
read_data.readline()
8.5.2. with语句是什么
with的基本语法是:
with 对象获取方式 as 对象:
对象的操作
可以在with后面获取到一个对象,然后在with的代码段中使用这个对象。但是这个对象并不是随便都可以的,需要实现两个魔术方法:
- _enter_: 通过with获取对象的时候调用,返回一个对象
- _exit_: 代码段执行结束,或者异常结束的时候会执行
class Demo:
def __enter__(self):
print("Demo Enter")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Demo Exit")
print(f"{exc_type = }")
print(f"{exc_val = }")
print(f"{exc_tb = }")
def test_exception(self):
print(10 / 0)
with Demo() as d:
d.test_exception()
print("aaa")
8.5.3. 文件操作中的with
在进行文件读写的时候,open返回的是_io.TextIOWrapper对象,而在这个对象中已经实现了__enter__和__exit__,并且在__exit__中完成了对数据流的关闭操作。因此,我们在进行文件操作的时候,就可以直接简化如下:
# 使用with操作文件,完成文件拷贝
def copy_file(src, dst):
with open(src, "rb") as read_data, open(dst, "wb") as write_data:
while len(data := read_data.read(1024)) != 0:
write_data.write(data)
write_data.flush()
copy_file("./相思.txt", "./相思2.txt")
8.6. pickle库
程序在运行的过程中,会在内存中存储很多的数据。这些数据会随着程序的结束而被释放掉。那如果我们希望下一次程序启动起来之后,依然能够使用到这些内存中的数据该怎么办呢?我们就需要将内存中的数据以文件的形式保存下来,下一次程序启动的时候,可以直接读取这个文件中的数据,加载到内存中继续使用。
在这里需要涉及到两个概念:
- **序列化:**就是将内存中的对象,转成字节序列的形式。然后可以将这些字节序列保存到文件中,或者传递给网络中的其他的机器。
- **反序列化:**就是通过一个字节序列,将其中的数据读取到内存中。
而pickle库,就是来实现序列化和反序列化的操作的!
import pickle
# pickle库是一个用来将内存中的数据转成字节序列,存储到文件中的库,通常以.pkl为后缀
# 序列化:将内存中的数据,转成字节序列
# 反序列化:将字节序列的数据解析成内存中的数据
# 序列化:dump
# 反序列化:load
with open("./file/data.pkl", "wb") as write_stream:
pickle.dump(10, write_stream)
pickle.dump("10", write_stream)
pickle.dump(b"10", write_stream)
pickle.dump([10, 20], write_stream)
pickle.dump({"key": "value"}, write_stream)
with open("./file/data.pkl", "rb") as read_stream:
value1 = pickle.load(read_stream)
value2 = pickle.load(read_stream)
value3 = pickle.load(read_stream)
value4 = pickle.load(read_stream)
value5 = pickle.load(read_stream)
print(type(value1), value1)
print(type(value2), value2)
print(type(value3), value3)
print(type(value4), value4)
print(type(value5), value5)
注意事项:
在使用dump和load进行序列化和反序列化的时候,需要保证dump的次数和load的次数、顺序是相同的。
使用dump在序列化对象的时候,操作的个数是不确定的,会导致load的次数也就不确定了。
- 如果load次数少了,会导致数据没有完全读取。
- 如果load次数多了,会出现异常
那么如何解决这样的问题呢?
最简单也是最常见的解决方式就是:将需要序列化的所有的对象存储到一个数据容器中,将这个数据容器序列化就可以了。