文件存储
保存数据有多种方式,其中最简单、成本最低的就是将数据保存在二进制或文本文件中。这些文件主要包括XML文件、CSV文件、JSON文件等。本章详细介绍Python API读写这些文件
一.操作文件的基本方法
1.1打开文件
open函数用于打开文件,通过该函数的第一个参数指定要打开的文件名(可以是相对路径,也可以是绝对路径)
如果open函数成功打开文件,那么该函数会返回一个TextIOWrapper对象,该对象中的方法可用来操作这个被打开的文件。如果要打开的文件不存在,会抛出FileNotFoundError异常。
open函数的第二个参数用于指定文件模式(用一个字符串表示),这里的文件模式是指操作文件的方式,如只读、写入、追加等
值 | 功能描述 |
---|---|
w | 写模式 |
r | 读模式 |
x | 写模式,创建一个文件,如果文件已存在,则报错 |
a | 追加模式 |
b | 二进制模式(可与其他模式结合使用) |
+ | 读/写模式(可与其他模式结合使用) |
其中 b 或者 + 可与其他模式结合使用需要说明下: |
- 如 rb 就表示读取一个二进制文件
- 如w+ 表示对打开的文件可读可写
- 如 wb+ 则表示对二进制文件可读可写,如果模式中不加 b则默认表示文本文件
补充:a+文件可读写,如果文件不存在,会创建一个新的文件,如果文件存在,会将要写入的内容添加到原文件的最后
1.2读文件和写文件
TextIOWrapper对象有如下4个常用的方法
1.write(string):像文件写入内容,该方法返回写入文件的字节数
2.read([n]):读取文件的内容,n是一个整数,表示从文件指针指定的位置开始读取的n个字节。如果不指定n,该方法就会读取从当前位置往后的所有的字节。该方法返回读取的数据
3.seek(n):重新设置文件指针,也就是改变文件的当前位置。使用write方法向文件写入内容后,需要调用seek(0),才能读取刚才写入的内容。这个意思就是写完马上要读的话,就要把指针移到前面去,不然读空
4.close():关闭文件
# 以写模式打开test1.txt文件
f = open('./files/test1.txt','w')
# 向test1.txt文件写入“I love ",运行结果:7
print(f.write('I love '))
# 向test1.txt文件写入“python",运行结果:6
print(f.write('python'))
# 关闭test1.txt文件
f.close()
# 以读模式打开test1.txt文件
f = open('./files/test1.txt', 'r')
# 从test1.txt文件中读取7个字节的数据,运行结果:I love
print(f.read(7))
# 从test1.txt文件的当前位置开始读取6个字节的数据,运行结果:python
print(f.read(6))
# 关闭test.txt文件
f.close()
try:
# 如果test2.txt文件不存在,会抛出异常
f = open('./files/test2.txt','r+')
except Exception as e:
print(e)
# 用追加可读写模式打开test2.txt文件
f = open('./files/test2.txt', 'a+')
# 向test2.txt文件写入”hello“
print(f.write('hello'))
# 关闭test2.txt文件
f.close()
# 用追加可读写模式打开test2.txt文件
f = open('./files/test2.txt', 'a+')
# 读取test2.txt文件的内容,由于目前文件指针已经在文件的结尾,所以什么都不会读出来
print(f.read())
# 将文件指针设置到文件开始的位置
# f.seek(0)
# 读取文件的全部内容,运行结果:hello
print("关闭f.seek(0)指针,看看读出什么",f.read())
# 关闭test2.txt文件
f.close()
try:
# 用写入可读写的方式打开test2.txt文件,该文件的内容会清空
f = open('./files/test2.txt', 'w+')
# 读取文件的全部内容,什么都没读出来
print(f.read())
# 向文件写入”How are you?“
f.write('How are you?')
# 重置文件指针到文件的开始位置
f.seek(0)
# 读取文件的全部内容,运行结果:How are you?
print(f.read())
finally:
# 关闭test2.txt文件,建议在finally中关闭文件
f.close()
1.3读行和写行
读写一整行是纯文本文件最常用的操作,尽管可以使用read和write方法加上行结束符来读写文件中的整行,但比较麻烦。因此,要读写一行或多行文本,建议使用readline、readlines和writelines方法。注意,并没有writeline方法,写一行文本直接使用write方法。
readline方法用于从文件指针当前位置读取一整行文本,也就是说,遇到行结束符停止读取文本,但读取的内容包括了行结束符。
readlines方法从文件指针当前的位置读取后面所有的数据,并将这些数据按行结束符分隔后,放到列表中返回
writelines方法需要通过参数指定一个字符串类型的列表,该方法会将列表中的每一个元素值作为单独的一行写入文件
import os
# 以读写模式打开urls.txt文件
f = open('./files/urls.txt','r+')
# 保存当前读上来的文本
url = ''
while True:
# 从urls.txt文件读一行文本
url = f.readline()
# 将最后的行结束符去掉
url = url.rstrip()
# 当读上来的是空串,结束循环
if url == '':
break;
else:
# 输出读上来的行文本
print(url)
print('-----------')
# 将文件指针重新设为0
f.seek(0)
# 读urls.txt文件中的所有行
print(f.readlines())
# 向urls.txt文件中添加一个新行
f.write('https://jiketiku.com' + os.linesep)
# 关闭文件
f.close()
# 使用'a+'模式再次打开urls.txt文件
f = open('./files/urls.txt','a+')
# 定义一个要写入urls.txt文件的列表
urlList = ['https://geekori.com' + os.linesep, 'https://www.google.com' + os.linesep]
# 将urlList写入urls.txt文件
f.writelines(urlList)
# 关闭urls.txt文件
f.close()
二.使用FileInput对象读取文本
如果需要读取一个非常大的文件,使用readlines函数会占用太多内存,为了解决这个问题,可以使用for循环和readline方法逐行获取,也可以使用fileinput模块中的input函数读取指定的文件
input方法返回一个FileInput对象,FileInput对象使用的缓存机制,并不会一次性读取文件的所有内容,所以比readlines函数更节省内存资源
本例使用fileinput.input方法读取urls.txt文件。
import fileinput
# 使用input方法打开urls.txt文件
fileobj = fileinput.input('./files/urls.txt')
# 输出fileobj的类型
print(type(fileobj))
# 读取urls.txt文件第1行
print(fileobj.readline().rstrip())
# 通过for循环输出urls.txt文件的其他行
for line in fileobj:
line = line.rstrip()
# 如果file不等于空串,输出当前行号和内容
if line != '':
print(fileobj.lineno(),':',line)
else:
# 输出当前正在操作的文件名
print(fileobj.filename()) # 必须在第1行读取后再调用,否则返回None
三.处理XML格式的数据
3.1读取与搜索XML文件
读取XML文件需要导入xml.etree.ElementTree模块,并通过该模块的parse函数读取XML文件
from xml.etree.ElementTree import parse
# 开始分析products.xml文件,files/products.xml是要读取的XML文件的名字
doc = parse('files/products.xml')
# 通过XPath搜索子节点集合,然后对这个子节点集合进行迭代
for item in doc.iterfind('products/product'):
# 读取product节点的id子节点的值
id = item.findtext('id')
# 读取product节点的name子节点的值
name = item.findtext('name')
# 读取product节点的price子节点的值
price = item.findtext('price')
# 读取product节点的uuid属性的值
print('uuid','=',item.get('uuid'))
print('id','=',id)
print('name', '=',name)
print('price','=',price)
print('-------------')
3.2字典转化为XML字符串
将字典转化为XML文件需要使用dicttoxml模块中的dicttoxml函数,在导入dicttoxml模块之前,需要先使用下面的命令安装dicttoxml模块
pip install dicttoxml
本例将字典类型变量转化为XML字符串,然后使用from xml.dom.minidom import parseString # 解析XML字符串,并用带缩进格式的形式将XML字符串写入persons.xml文件
import dicttoxml
from xml.dom.minidom import parseString # 解析XML字符串
import os
# 定义一个字典
d = [20,'names',
{'name':'Bill','age':30,'salary':2000},
{'name':'王军','age':34,'salary':3000},
{'name':'John','age':25,'salary':2500}]
# 将字典转换为XML格式(bytes形式)
bxml = dicttoxml.dicttoxml(d, custom_root = 'persons')
# 将bytes形式的XML数据按utf-8编码格式解码成XML字符串
xml = bxml.decode('utf-8')
# 输出XML字符串
print(xml)
# 解析XML字符串
dom = parseString(xml)
# 生成带缩进格式的XML字符串
prettyxml = dom.toprettyxml(indent = ' ')
# 创建files目录
os.makedirs('files', exist_ok = True)
# 以只写和utf-8编码格式的方式打开persons.xml文件
f = open('files/persons.xml', 'w',encoding='utf-8')
# 将格式化的XML字符串写入persons.xml文件
f.write(prettyxml)
f.close()
3.3XML字符串转换为字典
需要导入xmltodict模块
pip install xmltodict
本例使用xmltodict模块的parse函数分析这个XML字符串,如果XML格式正确,parse函数会返回与该XML字符串对应的字典对象
import xmltodict
# 打开products.xml文件
f = open('files/products.xml','rt',encoding="utf-8")
# 读取products.xml文件中的所有内容
xml = f.read()
# 分析XML字符串,并转化为字典
d = xmltodict.parse(xml)
# 输出字典内容
print(d)
f.close()
# pprint格式化打印出来
import pprint
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(d)
四.处理JSON格式的数据
JSON格式的数据可以保存数组和对象,JSON数组用一对中括号将数据括起来,JSON对象用一个对大括号将数据括起来。对象中的key和value之间要用冒号(:)分隔,key-value对之间用逗号(,)分隔。注意:key和字符串类型的值要用双引号括起来,不能使用单引号。
4.1JSON字符串与字典相互转换
将字典转换为JSON字符串需要使用json模块的dumps函数,该函数需要将字典通过参数传入,然后返回与字典对应的JSON字符串。
将JSON字符粗转化为字典可以使用下面两种方法
1.使用json模块的loads函数,该函数通过参数传入JSON字符串,然后返回与该JSON字符串对应的字典
2.使用eval函数将JSON格式1字符串当做普通的Python代码执行,eval函数会直接返回与JSON格式字符串对应的字典。
import json
# 定义一个字典
data = {
'name' : 'Bill',
'company' : 'Microsoft',
'age' : 34
}
# 将字典转换为JSON字符串
jsonStr = json.dumps(data)
# 输出jsonStr变量的类型
print('字典转化为json',type(jsonStr))
# 输出JSON字符串
print(jsonStr)
# 将JSON字符串转换为字典
data = json.loads(jsonStr)
print(type(data))
# 输出字典
print(data)
# 定义一个JSON字符串
s = '''
{
'name' : 'Bill',
'company' : 'Microsoft',
'age' : 34
}
'''
# 使用eval函数将JSON字符串转换为字典
data = eval(s)
print(type(data))
print(data)
# 输出字典中的key为company的值
print(data['company'])
# 打开products.json文件
f = open('files/products.json','r',encoding='utf-8')
# 读取products.json文件中的所有内容
jsonStr = f.read()
# 使用eval函数将JSON字符串转换为字典
json1 = eval(jsonStr)
# 使用loads函数将JSON字符串转换为字典
json2 = json.loads(jsonStr)
print(type(json1),json1) # 直接返回列表了
print(type(json2),json2)
print(json2[0]['name'])
f.close()
由于eval函数可以执行任何Python代码,如果JSON字符串中包含了有害的Python代码,执行JSON字符串可能会带来风险;所以尽量使用loads,不要使用eval函数
4.2将JSON字符串转化为类实例
loads函数不仅可以将JSON字符串转化为字典,还可以将JSON字符串转化为类实例。转换原理是通过loads函数的object_hook关键字参数指定一个类或一个回调函数。他们都会由loads函数传入由JSON字符串转化成的字典,也就是说,loads函数将JSON字符串转化为类实例本质上是先将JSON字符串转化为字典,然后再将字典转换为对象。区别是指定类时,创建类实例的任务由loads函数完成,而指定回调函数时,创建类实例的任务需要再回调函数中完成,前者更方便,后者更灵活。
import json
class Product:
# d参数是要传入的字典
def __init__(self, d):
self.__dict__ = d
# 打开product.json文件
f = open('files/product.json','r')
# 从product.json文件中读取JSON字符串
jsonStr = f.read()
# 通过指定类的方式将JSON字符串转换为Product对象
my1 = json.loads(jsonStr, object_hook=Product)
# 下面3行代码输出Product对象中相应属性的值
print('name', '=', my1.name)
print('price', '=', my1.price)
print('count', '=', my1.count)
print('-----------')
# 定义用于将字典转换为Product对象的函数
def json2Product(d):
return Product(d)
# 通过指定类回调函数的方式将JSON字符串转换为Product对象
my2 = json.loads(jsonStr, object_hook=json2Product)
# 下面3行代码输出Product对象中相应属性的值
print('name', '=', my2.name)
print('price', '=', my2.price)
print('count', '=', my2.count)
f.close()
4.3将类实例转化为JSON字符串
dumps函数不仅可以将字典转换为JSON字符串,还可以将类实例转化为JSON字符串。dumps函数需要通过default关键字指定一个回调函数,在转化的过程中,dumps函数会像这个回调函数传入类实例(通过dumps函数的第一个参数传入),而回调函数的任务是将传入的对象转化为字典,然后dumps函数再将字典转化为JSON字符串。
本例中的product2Dict函数的任务就是将Product类的实例转化为字典
import json
class Product:
# 通过类的构造方法初始化3个属性
def __init__(self, name,price,count):
self.name = name
self.price = price
self.count = count
# 用于将Product类的实例转换为字典的函数
def product2Dict(obj):
return {
'name': obj.name,
'price': obj.price,
'count': obj.count
}
# 创建Product类的实例
product = Product('特斯拉',1000000,20)
# 将Product类的实例转换为JSON字符串,ensure_ascii关键字参数的值设为True,
# 可以让返回的JSON字符串正常显示中文
jsonStr = json.dumps(product, default=product2Dict,ensure_ascii=False)
print(jsonStr)
4.4类实例列表与JSON字符串相互转换
前面转换的json对象都是单个对象,弱JSON字符串是一个类实例数组,或一个类实例的列表,也可以互相转换
import json
class Product:
def __init__(self, d):
self.__dict__ = d
f = open('files/products.json','r', encoding='utf-8')
jsonStr = f.read()
# 将JSON字符串转换为Product对象列表
products = json.loads(jsonStr, object_hook=Product)
# 输出Product对象列表中所有Product对象的相关属性值
for product in products:
print('name', '=', product.name)
print('price', '=', product.price)
print('count', '=', product.count)
f.close()
# 定义将Product对象转换为字典的函数
def product2Dict(product):
return {
'name': product.name,
'price': product.price,
'count': product.count
}
# 将Product对象列表转换为JSON字符串
jsonStr = json.dumps(products, default=product2Dict,ensure_ascii=False)
print(jsonStr)
4.5将JSON字符串转换为XML字符串
将JSON字符串转换为XML字符串其实只需要做一下中转即可,也就是先将JSON字符串转为字典,然后再使用dicttoxml模块中的dicttixml函数将字典转换为XML字符串
本例从products.json文件读取JSON字符串,并利用loads函数和dicttoxml函数,将JSON字符串转换为XML字符串
import json
import dicttoxml
f = open('files/products.json','r',encoding='utf-8')
jsonStr = f.read()
# 将JSON字符串转换为字典
d = json.loads(jsonStr)
print(d)
# 将字典转换为XML字符串
xmlStr = dicttoxml.dicttoxml(d).decode('utf-8')
print(xmlStr)
f.close()
五.CSV文件存储
CSV,全称是Comma-Separated Values,中文可以称为"逗号分隔值"或“字符分隔值”,CSV文件以纯文本形式存储表格数据。该文件是一个字符序列。CSV文件比Excel文件更加简洁,Excel文件是电子表格文件,是二进制格式的文件,里面包含了文本、数值、公式,甚至是VBA代码,而CSV文件中并不包含这些内容,只包含用特定字符分隔的文本,结构清晰简单,所以在很多场景下使用CSV文件保存数据是比较方便的
5.1写入CSV文件
本例演示使用csv模块API将数据写入CSV文件的完整过程
import csv
with open('files/data0.csv','w',encoding='utf-8') as f:
# 写入数据
writer = csv.writer(f)
writer.writerow(['产品ID','产品名称','生产企业','价格'])
writer.writerow(['0001','iPhone9','Apple',9999])
writer.writerow(['0002','特斯拉','特斯拉',12345])
writer.writerow(['0003','荣耀手机','华为',3456])
# 修改字段分隔符
with open('files/data11.csv','w',encoding='utf-8') as f:
writer = csv.writer(f,delimiter=';')
writer.writerow(['产品ID','产品名称','生产企业','价格'])
writer.writerow(['0001','iPhone9','Apple',9999])
writer.writerow(['0002','特斯拉','特斯拉',12345])
writer.writerow(['0003','荣耀手机','华为',3456])
# 一次性写入多行
with open('files/data22.csv','w',encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['产品ID','产品名称','生产企业','价格'])
writer.writerows([['0001','iPhone9','Apple',9999],
['0002', '特斯拉', '特斯拉', 12345],
['0003', '荣耀手机', '华为', 3456]])
# 写入字典形式的数据
with open('files/data33.csv','w',encoding='utf-8') as f:
fieldnames = ['产品ID','产品名称','生产企业','价格']
writer = csv.DictWriter(f,fieldnames=fieldnames)
writer.writeheader()
writer.writerow({'产品ID': '0001', '产品名称': 'iPhone9', '生产企业': 'Apple', '价格': 9999})
writer.writerow({'产品ID': '0002', '产品名称': '特斯拉', '生产企业': '特斯拉', '价格': 12345})
writer.writerow({'产品ID': '0003', '产品名称': '荣耀手机', '生产企业': '华为', '价格': 3456})
# 追加数据
with open('files/data00.csv','a',encoding='utf-8') as f:
fieldnames = ['产品ID','产品名称','生产企业','价格']
writer = csv.DictWriter(f,fieldnames=fieldnames)
writer.writerow({'产品ID': '0004', '产品名称': '量子战衣', '生产企业': '斯塔克工业', '价格': 99999999999})
5.2读取CSV文件
使用CSV模块的reader类,可以读取CSV文件。reader类的实例时可迭代的,所以可以用for循环迭代获取每一行的数据
如果使用Pandas,通过read_csv函数同样可以读取CSV文件的内容
本例演示使用CSV模块API读取CSV文件中数据的完整过程
import csv
with open('files/data.csv','r',encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print(row)
import pandas as pd
df = pd.read_csv('files/data.csv')
print(df)
其实XML文件、JSON文件、CSV文件本质上都是纯文本文件,只是文件的数据组织形式不同。