手写AI推出的全新保姆级从零手写自动驾驶CV课程,链接。记录下个人学习笔记,仅供自己参考。
本次课程主要学习文件IO的基本概念以及两个序列化模块pickle和json
课程大纲可看下面的思维导图。
在Python中,文件IO主要指读取文件内容和写入文件内容两个操作。IO是Input和Output的缩写,即输入和输出。Python提供了一些内置的函数和模块,可以实现文件的读写操作。常见的文件读写操作函数有:
open()
:打开文件并返回文件对象read()
:读取文件内容write()
:写入文件同时,Python中也有一些常见的文件操作模块,例如:
os模块
:用于处理文件和目录shutil模块
:提供了高层次的文件操作功能pathlib模块
:提供了对文件系统路径的面向对象编程接口使用这些函数和模块,我们可以方便地读取和写入文件,处理文件和目录
open()
函数是Python中用于打开文件的内置函数,其语法如下:(from chatGPT)
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
其中,
file
参数是打开的文件名mode
参数可选,用于指定文件的打开模式
r
以只读模式打开文件(默认);w
以写入模式打开文件(写模式会覆盖旧内容,如果不想覆盖请加上a标记);x
创建一个新文件以写入模式打开a
以追加模式打开文件;b
以二进制打开文件(处理二进制文件如图片和音频);t
以文本模式打开文件(默认,处理文本文件如.txt、.xml文件)rb
以二进制模式读取文件;wt
以文本模式写入文件buffering
参数可选,用于指定缓冲策略encoding
参数可选,用于指定文件的编码方式如utf-8
errors
参数可选,用于指定编码错误的处理方式newline
参数可选,用于指定文本模式下的行尾字符closefd
参数可选,用于指定是否在文件关闭时同时关闭文件描述符opener
参数可选,用于指定一个自定义的文件打开器当调用open()
函数时,会返回一个文件对象。对于文本文件,可以使用类似于读取字符串的方式读取和写入文件内容,例如:
f = open('example.txt', 'r')
lines = f.readlines()
for line in lines:
print(line)
f.close()
with open('example.txt', 'r') as f:
content = f.read()
with open("example.txt", "r") as f:
line = f.readline()
while line:
print(line)
line = f.readline()
with open('output.txt', 'w') as f:
f.write('Hello, world!')
在Python文件IO中,f.read()
、f.readlines()
以及f.readline()
都是读取文件内容的方法,不同之处在于返回的结果格式不同。
f.read(size=-1)
方法读取整个文件或者指定大小的文件内容,返回一个字符串
f.readlines()
方法读取整个文件内容并返回一个列表,其中每个元素都是文件的一行(包括换行符)
f.readline()
方法读取文件的一行内容,并将其作为一个字符串返回(包括换行符),可以连续调用来逐行读取整个文件
需要注意的是,f.read()
和f.readlines()
方法都会读取文件的全部内容,如果文件过大,可能会导致内存溢出或者卡死程序,因此需要谨慎使用。对于较大的文件,可以使用循环逐行读取或者使用f.readline()
方法一次读取一行。
在Python文件IO中,open()
函数和with open()
语句都可以用于文件的读写操作,但是二者的区别在于with
语句在完成操作后会自动关闭文件,而open()
函数需要手动调用close()
方法来关闭文件,如果忘记关闭文件会导致释放资源不及时,而资源是有限制的,如果过多的打开不释放,会造成资源消耗过度而程序或系统异常。
因此,最好使用with
语句来自动管理文件的打开和关闭,以防止文件句柄泄露和资源浪费。with
语法结构如下:
with 对象 as 变量:
代码块
等价于
变量 = 对象.__enter__()
try:
代码块
except Exception as exception:
pass
对象.__exit__(exception)
write
函数是Python中用于将指定内容写入文件的内置函数,其语法如下:
file.write(str)
其中,file
代表要写入的文件对象,可以使用open
函数打开一个文件后获得,str
是要写入的内容
以下是一个简单的示例,用于将一个字符串写入到指定目录的文件中:
import os
os.makedirs("a/b/c", exist_ok=True)
with open("a/b/c/example.txt", 'w') as f:
f.write("Hello world!")
在这个示例中,我们首先创建了利用os.makedirs
创建了一个多级目录,然后以写的方式打开了一个文件,同时使用write
函数将字符串Hello world!写入到文件中。如果该文件不存在,则会自动创建一个新文件。
拓展之os.makedirs():该函数用于创建多级目录。如果需要创建的目录的上级目录不存在,它可以自动创建确实的上级目录。
函数定义为:
os.makedirs(name, exist_ok=False)
参数说明:
name
:需要创建的目录路径,可以是绝对路径或相对路径exist_ok
:如果设置为True
,在目录已经存在的情况下不会引发异常,否则会抛出FileExistsError
异常示例:
import os
# 创建单个目录
os.mkdir("mydir")
# 创建多级目录
os.makedirs("mydir/subdir1/subdir2", exist_ok=True)
下面的示例中演示了二进制文件的写和读操作:
import struct
with open("./test.bin", 'wb') as f:
f.write(struct.pack("i", 123))
with open("./test.bin", 'rb') as f:
print(struct.unpack("i", f.read()))
其中,struct.pack(format, v1, v2, ...)
函数可以将数据按照给定的格式(format)进行打包,返回打包后的字节流
format
是格式化字符串,v1,v2,...
表示需要打包的数据format
中的每个格式符都对应一个特定的C语言数据类型,例如"i"表示int,"f"表示float,"s"表示字符串struct.unpack(format, string)
函数则是将打包后的二进制数据转换为Python数据类型
format
表示打包格式字符串,string
表示打包后的二进制数据内容:读取一个xml文件,并解析获得其中的信息,具体需要解析的内容如下:
保存xml文件中size标签下的width、height内容
保存xml中objec标签下的name、xmin、ymin、ymax内容,并将name转换为对应的标签索引
利用保存的内容,将xmin、ymin、xmax、ymax转换为中心点宽高的形式,并保存到txt文件中
首先先来看下xml中的内容:
<annotation>
<folder>drinkfolder>
<filename>dr105.jpgfilename>
<path>C:\Users\Admin\Desktop\drink\dr105.jpgpath>
<source>
<database>Unknowndatabase>
source>
<size>
<width>293width>
<height>220height>
<depth>3depth>
size>
<segmented>0segmented>
<object>
<name>drinkname>
<pose>Unspecifiedpose>
<truncated>0truncated>
<difficult>0difficult>
<bndbox>
<xmin>14xmin>
<ymin>53ymin>
<xmax>77xmax>
<ymax>130ymax>
bndbox>
object>
<object>
<name>drinkname>
<pose>Unspecifiedpose>
<truncated>1truncated>
<difficult>0difficult>
<bndbox>
<xmin>243xmin>
<ymin>29ymin>
<xmax>293xmax>
<ymax>89ymax>
bndbox>
object>
annotation>
解析xml文件并转换保存至txt文件的示例代码如下:
def xml_parse(xml_file):
'''
解析xml文件中的width、height、name、xmin、xmax、ymin、ymax
'''
labels = {'smoke' : 0, 'drink' : 1}
size = {}
boxes = []
# 读取xml文件
with open(xml_file, 'r') as f:
lines = f.readlines()
box = []
for line in lines:
line = line.replace('\t', '') # 去除多余的空格
line = line.replace('\n', '')
if line[:7] == '' : # 获取图像宽高
width = int(line[7:-8])
size['width'] = width
if line[:8] == '' :
height = int(line[8:-9])
size['height'] = height
if len(box) == 5:
boxes.append(box)
box = []
if line[:6] == '' : # 获取目标属性label、xmin、ymin、xmax、ymax
name = line[6:-7]
box.append(labels[name])
if line[:6] == '' :
xmin = int(line[6:-7])
box.append(xmin)
if line[:6] == '' :
xmax = int(line[6:-7])
box.append(xmax)
if line[:6] == '' :
ymin = int(line[6:-7])
box.append(ymin)
if line[:6] == '' :
ymax = int(line[6:-7])
box.append(ymax)
return boxes, size
def convert(boxes, size):
'''
将boxes进行转换 xmin、ymin、xmax、ymax ==> x、 y、 w、 h
'''
dw = 1. / size['width']
dh = 1. / size['height']
for box in boxes: # box ===> [label,xmin,ymin,xmax,ymax]
x = (box[1] + box[3]) / 2
y = (box[2] + box[4]) / 2
w = box[3] - box[1]
h = box[4] - box[2]
box[1] = x * dw
box[2] = y * dh
box[3] = w * dw
box[4] = h * dh
return boxes
def save_txt(save_path, boxes):
'''
将转换好的boxes信息保存为txt文件
'''
with open(save_path, 'w') as f:
for box in boxes:
line = ' '.join([str(elem) for elem in box])
if box == boxes[-1]: # 如果是最后一行,则不加入换行符
f.write(line)
else:
f.write(line + '\n')
boxes, size = xml_parse("./Annotations/dr105.xml")
print(f"boxes = {boxes}")
print(f"size = {size}")
boxes = convert(boxes, size)
print(f"convert boxes = {boxes}")
save_txt("./Annotations/dr105.txt", boxes)
在上述示例代码中,xml_parser
解析函数用于获取解析的boxes和size信息,并将boxes保存为列表形式,将size保存为字典形式,conver
函数用于将获取到的boxes信息进行转换,而save_text
函数用于将转换好的boxes信息保存为txt文件。
运行输出结果如下:
问题:对于每个目标属性的获取,代码中使用了多个if语句,不够简洁和优雅,可以使用正则表达式或者基于ElementTree的xml解析库来简化代码(suggestion from chatGPT)
优化1:可以使用re模块的findall()
函数一次性匹配出所有需要的信息,具体实现如下:
import re
def xml_parse_regex(xml_file):
labels = {'smoke' : 0, 'drink' : 1}
size = {}
boxes = []
with open(xml_file, 'r') as f:
content = f.read().replace('\n', '') # 去除多余的换行符
# 使用正则表达式匹配需要的信息
width = int(re.findall(r'(\d+) ', content)[0])
height = int(re.findall(r'(\d+) ', content)[0])
size['width'] = width
size['height'] = height
for box in re.findall(r'', content, re.S):
name = re.findall(r'(.*?) ', box)[0]
xmin = int(re.findall(r'(\d+) ', box)[0])
xmax = int(re.findall(r'(\d+) ', box)[0])
ymin = int(re.findall(r'(\d+) ', box)[0])
ymax = int(re.findall(r'(\d+) ', box)[0])
boxes.append([labels[name], xmin, ymin, xmax, ymax])
return boxes, size
上述代码使用了正则表达式来解析xml文件,使用re.findall()
函数和正则表达式来匹配需要的信息,其中,正则表达式中的.*?
表示匹配任意字符,re.S
表示开启多行匹配模式。最后,将匹配到的信息转换为相应的数据类型,并将其存储到size
和boxes
中,最终返回这两个数据结构。
该代码相比之前的代码更加简洁,使用了正则表达式来简化匹配过程,减少了代码量和复杂度。但是,正则表达式也可能存在一些缺陷,如难以处理嵌套标签和复杂的文本内容,因此在实际应用中需要根据情况进行选择。
优化2:使用Python标准库中的ElementTree来解析XML文件,代码如下:
import xml.etree.ElementTree as ET
def xml_parse_ET(xml_file):
labels = {'smoke' : 0, 'drink' : 1}
size = {}
boxes = []
# 读取xml文件
tree = ET.parse(xml_file)
root = tree.getroot()
# 获取图像宽高
size['width'] = int(root.find('size/width').text)
size['height'] = int(root.find('size/height').text)
# 获取目标属性label、xmin、ymin、xmax、ymax
for obj in root.iter('object'):
box = []
box.append(labels[obj.find('name').text])
bbox = obj.find('bndbox')
box.append(int(bbox.find('xmin').text))
box.append(int(bbox.find('ymin').text))
box.append(int(bbox.find('xmax').text))
box.append(int(bbox.find('ymax').text))
boxes.append(box)
return boxes, size
上述代码使用了Python内置库xml.etree.ElementTree
来解析XML文件。具体步骤如下:
1.通过ET.parse(xml_file)
函数读取XML文件,并将其解析为一个ElementTree对象
2.通过tree.getroot()
获取XML文件的根元素对象,即\
3.通过root.find('size/width(height)').text
获取图像宽高
4.通过root.iter('object')
获取所有目标元素对象
5.对于每个目标元素对象,通过obj.find('name').text
获取目标的标签,通过bbox.find('xmin/ymin/xmax/ymax')
获取目标的位置信息,并将它们存储到一个列表中
6.返回目标和图像大小信息
使用ElementTree库解析XML文件的代码相比正则表达式和原生读取文件的方法更为简洁,易于阅读和维护。同时,它还提供了更为方便的元素遍历和查找功能,能够大大提高XML文件的解析效率。
re.findall()
函数是Python中re
模块提供的一种正则表达式匹配方法,用于搜索匹配某个字符串中所有符合正则表达式规则的子字符串,并返回一个列表。其语法结构如下:
re.findall(pattern, string, flags=0)
其中,pattern
是一个正则表达式,string
是待匹配的字符串,flags
是一个可选参数,表示正则表达式的匹配模式。函数返回一个列表,其中包含所有符合正则表达式规则的子字符串。
在pattern
中,可以使用各种正则表达式的元字符和限定符,用于描述需要匹配的模式。例如,
(\d+)
表示匹配一个或多个数字,并将其作为一组提取出来。+
表示匹配一个或多个前面的模式,\d
表示匹配任意数字(.*?)
表示匹配任意字符,?
表示匹配0个或1个前面的模式,.*
表示匹配任意个任意字符。?
加在*
或+
后面表示非贪婪模式,尽可能少的匹配。(看的我头疼)下面是一个简单(似乎也没有那么简单)的示例代码:
import re
string = "Age: 25, Name: John"
age = re.findall(r"\d+", string)[0]
name = re.findall(r"Name: (.*?)$", string)[0]
print(f"Age: {age}") # 输出:Age:25
print(f"Name: {name}") # 输出:Name:John
在这个’简单’的示例代码中,第一个正则表达式\d+
匹配年龄(一个或多个数字),第二个正则表达式Name: (.*?)$
匹配姓名,其中(.*?)
是一个非贪婪匹配,可以匹配到Name:
到行末之间的任意字符。
这正则表达式看的我眼晕,感觉和解密差不多。
ElementTree
是Python标准库中的一个解析XML的模块。它提供了一种轻量级的处理XML文档的方式,能够高效地读取、写入和操作XML文件。
使用ElementTree解析XML文件的主要步骤包括以下几个:
ET.parse()
方法将XML文件解析成一个ElementTree对象find()
和iter()
)查找自己需要的节点,并获取其内容或属性值下面是一个简单的示例,假设我们有以下的XML文件example.xml:
<root>
<person>
<name>Johnname>
<age>30age>
person>
<person>
<name>Janename>
<age>25age>
person>
root>
我们可以使用ElementTree库获取所有person
节点的name
和age
属性,如下所示:
import xml.etree.ElementTree as ET
# 读取XML文件
tree = ET.parse('example.xml')
# 获取根元素
root = tree.getroot()
# 遍历所有person节点
for person in root.iter('person'):
# 获取name和age属性值
name = person.find('name').text
age = person.find('age').text
print(f"Name: {name}, Age: {age}")
输出结果为:
Name: John, Age: 30
Name: Jane, Age: 25
XML(eXtensible Markup Language)是一种用于存储和传输数据的标记语言,它采用标签(tag)和属性(attribute)的方式来描述数据。XML最初的设计目的是为了解决不同系统间数据交换的问题,同时也被广泛用于数据存储和Web服务等领域。(all from chatGPT)
与HTML类似,XML也是由标签和文本组成的,不同之处在于XML标签可以自定义,而HTML的标签则是预定义好的。XML可以被用于描述任何类型的数据,比如文本、数字、日期、图像、音频等等。XML的语法规则非常简单,它由一系列嵌套的元素组成,每个元素由一个开始标签、一个结束标签和一些子元素或者文本组成。
下面是一个XML文件的例子:
<bookstore>
<book category="children">
<title lang="en">Harry Pottertitle>
<author>J.K. Rowlingauthor>
<year>2005year>
<price>29.99price>
book>
<book category="web">
<title lang="en">Learning XMLtitle>
<author>Erik T. Rayauthor>
<year>2003year>
<price>39.95price>
book>
bookstore>
在上述例子中,第一行指定了XML版本和编码方式。接下来是一个bookstore
元素,它包含两个book
元素,每个book
元素包含了title
、author
、year
和price
四个子元素,其中title
元素有一个lang
属性,book
元素有一个category
属性。
XML具有良好的可扩展性和灵活性,可以根据需要添加、修改、删除元素和属性。同时XML还支持数据验证和处理,可以通过XSD、DTD、XPath等技术对XML进行验证和处理。
在前面的内容中,对于一些基本数据类型,若想写出到二进制储存,需要转化到二进制才能够写出,可以借用
struct.pack()
函数进行转换,但是对于数组、元组这种数据类型,则情况比较复杂,这时我们可以使用pickle模块来进行操作。Pickle模块是Python标准库中的一个模块,用于序列化和反序列化Python对象。序列化是将Python对象转化为二进制数据流的过程,而反序列化则是将二进制数据流转换为Python对象的过程。
Pickle模块提供了两个主要的函数:pickle.dump()
和pickle.load()
。其中,pickle.dump(obj,file)
用于将Python对象obj
序列化并写入到二进制文件file
中,而pickle.load(file)
则用于从二进制文件file
中读取数据并反序列化为Python对象。
下面是一个简单示例,使用pickle模块来实现Python对象的序列化和反序列化:
import pickle
# 储存对象 序列化
with open("./test.bin", "wb") as f:
pickle.dump([123, "Python", {"name": "Tom"}], f)
# 读取对象 反序列化
with open("./test.bin", "rb") as f:
data = pickle.load(f)
print(data) #输出:[123, 'Python', {'name': 'Tom'}]
当你需要把Python中的某个对象储存下来发给其他人,可以使用pickle.dump
,然后其他人可以通过pickle.load
进行读取
json模块是一种用于将结构化数据序列为文本格式的库,它支持list、tuple、dict、string、int、float这些基本的数据类型。而对于其他类型如class等,默认是不支持的,如果要支持需要定制去开发
在Python中,json模块用于处理JSON格式的数据,其中包含了许多用于序列化和反序列化JSON数据的函数。
json.dump()
函数用于将Python对象序列化为JSON格式的字符串,并将其写入文件中。json.load()
函数用于从JSON格式的文件中读取数据,并将其反序列化为Python对象。
下面是一个简单示例,使用json模块来实现Python对象的序列化和反序列化:
import json
# 储存对象 序列化
with open("./test.json", "w") as f:
json.dump([123, "Python", {"name": "Tom"}], f)
# json.dumps
# 读取对象 反序列化
with open("./test.json", "r") as f:
data = json.load(f)
# json.loads
print(data)
本次课程学习了文件的读取操作,并会使用ElementTree库解析XML文件,这对于后面标签格式的转换非常有帮助。同时学习了两个序列化模块pickle和json,其中pickle模块是二进制序列化的library,json模块是文本序列化的library。