python - 啃书 第九章 文件访问

概述

计算机文件是存储在外部存储器上的数据集合。通常计算机处理的大量数据都是以文件的形式组织存放的,操作系统也是以文件为单位对数据进行管理的。

每个文件都有一个文件名,文件名由基本名和扩展名组成,不同类型的文件扩展名也不相同。
文本文件扩展名.txt,Word文档扩展名一般为.docx,可执行文件扩展名.exe,C语言源文件扩展名.c等

在计算机系统中,文件种类众多,其处理方法和用途各不相同。在计算机中,文件一般按文件内容和信息存储形式分类。

1、按文件内容分类

计算机文件按文件内容可分为程序文件和数据文件。

程序文件存储的是程序,包括源文件和可执行文件,如Python源文件(扩展名为.py),C++源文件(扩展名为.cpp),可执行文件(扩展名为.exe)等都是程序文件。

数据文件存储的是程序运行所需要的各种数据,如文本文件(.txt)、Word文档(.docx)、Excel工作薄(.xlsx)等

2、按信息存储形式分类

计算机文件按存储信息形式可分为文本文件和二进制文件。文本文件是基于字符编码的文件,常见编码方式有ASCII编码、Unicode编码等,可以使用文本编辑软件建立和修改,如记事本、EditPlus、UltraEdit等。常见的文本文件有文本格式文件(.txt)、网页文件(.html)等。

二进制文件中存放的是各种数据的二进制编码,含有特殊的格式及计算机代码,必须用专用程序打开。
常见的二进制文件有数据库文件、图像文件、可执行文件、音频文件等。

同样的数据,存放在不同类型的文件中,其存储格式和所需的空间大小也不相同。例如整数567,若以ASCII形式存储,需要3个字节;若以二进制形式存储,则一般需要2个字节(有的需要4个字节)

ASCII码存储形式: 35 36 37
二进制存储形式: 02 37

文本文件访问

在Python中,访问文本文件可以分为三步
(1)用open()函数打开一个文件,返回一个文件对象
(2)调用文件对象中的函数对文件内容进行读或写等操作
(3)调用文件对象的close()函数关闭文件

打开文件

1、open()函数

Python内置的open()函数用于打开一个文件,返回一个文件对象:
open(filename[,mode])
这里只是句柄,只要不读取文件内容,并不会增加内存消耗!

其中,filename为要打开的文件名称及路径。mode为文件访问模式:

文件访问模式

模式 简述 描述
r 只读,默认 以只读方式打开文件,默认模式
rb 只读,二进制 以二进制格式打开一个文件用于只读,文件指针将会放在文件的开头
r+ 读写 打开一个文件用于读/写
rb+ 读写,二进制
w 写建 打开一个文件用于写入,如果该文件存在,则打开文件;否则,创建新文件
wb 写建,二进制 以二进制格式打开一个文件用于写入。
w+ 读写建
wb+ 读写建,二进制
a 加建 打开一个文件用于追加。如果该文件存在,则文件指针将会放在文件的结尾;否则,创建新文件
ab 加建,二进制 以二进制格式打开一个文件用于追加
a+ 读写加建 打开一个文件用于读/写。如果该文件存在,则文件指针将会房子啊文件的结尾;否则创建新文件
ab+ 读写加建,二进制

以上区别:写与读写,读是确实可以读的,但是读是从指针开始读,所以如果想读取全部内容,需要将指针指向开头

用open()函数打开一个文件,查看返回文件对象的相关信息

file=open(r"./res/QB.png","w")
print("文件名:",file.name)
print("访问模式:",file.mode)
print("是否已关闭:",file.closed)
file.close
print(file)
#
文件名: ./res/QB.png
访问模式: w
是否已关闭: False
<_io.TextIOWrapper name='./res/QB.png' mode='w' encoding='cp936'>

程序运行成功后,在"工作目录/res/"下创建新文件QB.png,如果该文件已经存在,则会覆盖该文件。于是我还要在网上再下载一次这个图片,笑哭!

文件路径“/”等同于“\”,经实验,“\”也行

!!!这里看完之后章节后,再回来看,整理补充下表格

2、with-as语句

在上卖弄代码中,在文件打开或者后面的读、写等操作的过程中都可能发生错误,即使写了关闭文件的语句也不一定能够正常执行,从而导致不能正常关闭文件,缓存信息可能会意外丢失,文件可能会损坏。为了避免发生这种情况,推荐使用Python中的with-as语句,该语句可以避免这种情况的发生。

with-as语句实用于对资源进行访问的场合,可确保不管在使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,如文件使用后自动关闭等。

with open(filename[,mode]) as file:
语句块

其中file为open()函数返回的文件对象。
with-as语句也支持同时打开多个文件用于读写

with open(r"./res/QB.png","w+") as file:
    num=file.write("Go Go Go")
print("写入字符%d个"%num)

但,我仅对上面的w+进行了测试,貌似并没有所说的那么强大,只是file.close集成在了这个写法中

当这个方法开始时,文件已经被重建了,只是在完成这个语法块时,会自动file.close而已!

文件操作

在Python中,文本文件的操作(如读、写、关闭等)由文件对象的相关函数完成。

1、写文件

使用函数file.write()或file.writelines()想文件中写入内容

(1)file.write()

向文件中写入字符,并返回写入字符的数目
file.write(str)

with open("Shakespeare.txt","w") as file:
    num=file.write("黑夜无论怎样悠长\n白昼总会到来")
    print("向文件中写入字符%d个!"%num)

str=input("请输入内容:")
with open("Shakespeare.txt","a") as file:
    file.write("\n")
    num=file.write(str)
    print("向文件中追加字符%d个!"%num)

t=("7234",5678)
with open("77.txt","w") as file:
    num=file.write(str(t))
print("写入%d个字符,写入元组成功"%num)

但是这里发生了个问题,第三段代码报错:TypeError: ‘str’ object is not callable

原因是前面将str定义了个变量,所以连续运行下,这里报错的str是一个变凉,而非最初的str()函数

jupyter只能重启服务才能正常运行第三段

为了防止错误,这里我是用s还是str1呢?

(2)file.writelines()

向文件中写入一个字符序列
file.writelines(sequence)

ls={"但尝不出味道\n","喜欢椰奶,","1","2","3","4"}
with open("77.txt","w") as file:
    file.writelines(ls)
print("写入字符序列成功")
#
但尝不出味道
喜欢椰奶,4321

ls={"A":"a","C":"c","B":"b"}
#
ACB

相比上上段代码,用str(seq),这段代码是吧内容不添加seq标识,挨个打出来的!但注意,元素必须是str,不能是int,如果输入是字典,相当于先执行类似如list()的seq转化方法

书中的例题中,是吧print下载with-as的语句块中的,个人认为不合适,需要写在外面才对,万一执行完print就立刻宕机呢!

2、读文件

从文件中读取内容:file.read()、file.readline()、file.readlines()

(1)file.read()

读取一个文件的内容,返回一个字符串对象
file.read([size])
size为要读取内容的数目,是一个可选的数字类型参数。
当size被忽略或为负时,则该文件的所有内容都将被读取且返回

这里,当文件是由python创建的,将会以ANSI格式存储,即ASCII+UTF,当读取方式时r,则数目时字符数,当读取方式时rb,数目就是字节数

with open("Shakespeare.txt","r") as file:
    s=file.read(17)
print(s)
#
黑夜无论怎样悠长
白昼总会到来
a
with open("Shakespeare.txt","r") as file:
    for i in file:
        print(i)
#
黑夜无论怎样悠长

白昼总会到来

aliwang
(2)file.readline()

从文件中读取一行,返回一个字符串
file.readline([size])
size为要读取内容的数目,可选
当读取模式时b,支持\n识别

with open("Shakespeare.txt","r") as file:
    s=file.readline()
print(s)
#
黑夜无论怎样悠长

他是从当前指针开始读取一行,可选参数也是对于输出中的字符数量来说
若要读取至文件结束,参考后面:搜索:我们已经把提瓦特的生物灭绝了无数次,

(3)file.readlines()

读取并返回该文件中包含的所有行,以字符串列表形式返回
file.readlines([sizeint])
sizeint为要读取的字节数,可选

with open("Shakespeare.txt","r") as file:
    s=file.readlines(15)
print(s)
#
['黑夜无论怎样悠长\n', '白昼总会到来\n']

file.read(15)
#
黑夜无论怎样悠长
白昼总会到来

file.read(16)
#
黑夜无论怎样悠长
白昼总会到来


file.readlines(16)
#
['黑夜无论怎样悠长\n', '白昼总会到来\n', 'aliwang']

在read中,16加上了"\n",但在readlines中,16,这个不好解释,毕竟我们真的不知道他的机制,但是可以给自己的概念定义,这个\n属于第三行,或者readlines是从0开始索引的等等,结果就是当16指向行(段落)尾部时,这里会把下一行(段)算进来

with open("Shakespeare.txt","w") as file:
    file.write("""黑夜无论怎样悠长
白昼总会到来
aliwang
""")

with open("Shakespeare.txt","r") as file:
    s=file.readlines()
print(s)
#
['黑夜无论怎样悠长\n', '白昼总会到来\n', 'aliwang\n']

但如果文本尾部换行但没有字符后,并不会显示""空字符

现在回看开始的文件访问模式

我那时的疑问是,w是写,w+是读写,这么说w的时候,其实并未读取。那w+的时候其实是先读取文件的,突然发现,意义呢,既然w+是注定要覆盖写入的,那么,emmm

with open("Shakespeare.txt","w+") as file:
    s=file.read()
    print(s)
    file.write("test")
#看,啥都没有

并不会先读取了test

即便把w改成a,也是无法显示的,除非是r,但此时又不能执行write,也就是说其实这个读/写存在异议,我上网查查看!

Python文件操作中的a,a+,w,w+,rb+,rw+,ra+几种方式的区别
都明白了,关键是指针,而file.read()就是从指针位置开始读的,

with open("Shakespeare.txt","w+") as file:
    file.write("1234567890\n一二三四五六七八九十")
    file.seek(0)
    print(file.read())
with open("Shakespeare.txt","a+") as file:
    file.seek(0)
    s=file.read()
    print(s)
with open("Shakespeare.txt","r+") as file:
    file.write("6666")
    file.seek(0)
    s=file.read()
    print(s)
    file.write("999")
    file.seek(0)
    s=file.read()
    print(s)
    file.seek(0)
    file.write("999")
    file.seek(0)
    s=file.read()
    print(s)
    file.seek(0)
    s=file.read(3)
    print(s)
    s=file.read()
    print(s)
#
1234567890
一二三四五六七八九十
1234567890
一二三四五六七八九十
6666567890
一二三四五六七八九十
6666567890
一二三四五六七八九十999
9996567890
一二三四五六七八九十999
999
6567890
一二三四五六七八九十999

无论读写,指针都会随之变化

3、其他文件操作

(1)file.close():关闭文件。将缓冲区内容写入文件,并关闭。
(2)file.flush():刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件,而不是被动的等待输出缓冲区写入。
(3)file.fileno():返回一个整型的文件描述符(file descriptor,FD,整型)
(4)file.isatty():如果文件连接到一个终端设备,则返回True,否则返回False
(5)file.tell():返回当前指针位置
(6)file.seek():重定位未见对象的指针位置
(7)file.truncate([size]):从文件的首行首字符开始截断,截断文件为size个字符,无size标识从当前位置截断;截断之后后面的所有字符被删除。(指针之后的内容被删除,之前的内容取size个字符)
即,设定文件结束位置!

为了应对书中的例子,我使用%%writefile重写文件,结果发现使用这种方法重写文件,是以UTF-8格式写入的,无法直接按照字符串读取,需要转码

%%writefile Shakespeare.txt
我们已经把提瓦特的生物灭绝了无数次,
我们锄了大地无数层皮,
我们杀死了无数的愚人众与盗宝团的人,
我们偷窃了无数的财富与物品,
我们炸了无数的鱼,破坏了无数的公共财产。
我们偷窃,我们杀戮,我们破坏。
比起我们来说,丘丘人、深渊教团、愚人众造成的破坏就是毛毛雨。

with open("Shakespeare.txt","r",encoding="UTF-8") as file:
    data=file.readline()
    print(data.strip()) # 是否还记得这个,删除前后空格,带参数的话,就顺便删除参数例如data.strip("我们")
    print("输出一行后的文件指针在:",file.tell())
    file.seek(0)
    print("用seek()将文件指针放回开始处:",file.tell())
    print("再次输出:",file.readline())
    print("继续输出:",file.readline())
    while 1:
        l=file.readline()
        if l=="":break
        print(l)
#
我们已经把提瓦特的生物灭绝了无数次,
输出一行后的文件指针在: 56
用seek()将文件指针放回开始处: 0
再次输出: 我们已经把提瓦特的生物灭绝了无数次,

继续输出: 我们锄了大地无数层皮,

我们杀死了无数的愚人众与盗宝团的人,

我们偷窃了无数的财富与物品,

我们炸了无数的鱼,破坏了无数的公共财产。

我们偷窃,我们杀戮,我们破坏。

比起我们来说,丘丘人、深渊教团、愚人众造成的破坏就是毛毛雨。

这里的指针位置是按照字节算的,如果我们将文本转化成ANSI格式的话,指针位置此时在38

这里我们可以把文本当作从0开始索引,当指针在0时,他读写的目标就是从0即第一个字节/字符开始的。

话说编程就是有好多改bug的机会,如果这些代码都变成笔试题,读代码的话,嘿嘿!

二进制文件访问

对于二进制文件,不能使用记事本等文本编辑软件进行读/写,也无法通过Python的文件对象直接读取和理解二进制文件中的内容。要想正确读/写二进制文件,也必须理解二进制文件的结构、序列化规则和反序列化规则

所谓二进制文件序列化,是指把内存中的数据对象在不丢失其类型的情况下转换成对应的二进制信息的过程。所谓二进制文件反序列化,是指把序列化后的二进制信息准确无误的恢复到原来数据对象的过程

同文本文件访问过程相似,二进制文件访问也分为三个步骤:
(1)打开二进制文件
(2)访问二进制文件
写操作是将要写入的内容序列化后写入文件的过程。
读操作时将二进制文件内容读出反序列化的过程。
(3)关闭二进制文件

在Python中访问二进制文件的常用模块又Pickle、Struct、Marshal、Shelve等

使用Pickle模块读/写二进制文件

Pickle模块只能在Python中使用。
在Python中,几乎所有的数据类型(如列表、字典、集合、类等)都可以用Pickle模块实现序列化。
Pickle模块用于序列化和反序列化的函数有两个:pickle.dump()和pickle.load()函数。
pickle:浸封,目前查到的最接近功能意思的
dump:转储,倾倒

1、pickle.dump()
序列化数据对象,并将该对象写入二进制文件中
pickle.dump(data,file[,protocol])
data为要写入二进制文件的数据对象
file为创建或打开的文件对象
protocol为序列化模式,可选

import pickle
list1=[2,4,5,7]
tuple1=("music","game","sports")
data=[list1,tuple1]
with open("pickle_file.dat","wb") as pickle_file:
    for i in data:
        pickle.dump(i,pickle_file)
print("写入数据成功!")

2、pickle.load()
反序列化数据对象,即将二进制文件中的内容解析为一个数据对象
pickle.load(file)
file为打开的二进制文件对象

import pickle
with open("pickle_file.dat","rb") as pickle_file:
    data1=pickle.load(pickle_file)
    print("data1:",data1)
    data2=pickle.load(pickle_file)
    print("data2:",data2)
#
data1: [2, 4, 5, 7]
data2: ('music', 'game', 'sports')

由此看来,load其实类似于readline,而dump每次写入,相当于在末尾加了个\n

然后看写入信息:

python - 啃书 第九章 文件访问_第1张图片
总的来说,就是数据类型标识+数据内容。

pickle的方法不多,但是文档却很长!
那核心要记住:
dump(object, file)
dumps(object) -> string
load(file) -> object
loads(bytes) -> object

import pickle
print(pickle.dumps("123"))
print(pickle.dumps("456"))
with open("pickle1.dat","wb+") as p:
    pickle.dump("123",p)
    pickle.dump("456",p)
    p.seek(0)
    pp=p.read()
    print(pp)
    print(t:=pickle.loads(pp))
    p.seek(int(p.tell()/2))
    print(t:=pickle.loads(p.read()),type(t))
#
b'\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03123\x94.'
b'\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03456\x94.'
b'\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03123\x94.\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03456\x94.'
123
456 <class 'str'>

这里loads读取的是字节,而且只显示第一个数据,想要获得所有的数据,目前看来只能写一个方法。但是pickle貌似并没有返回对应的二进制长度!?上面因为我知道长度是完全相等的两段。
那如何只知道btyes的情况下,高效的读取其内容呢?只要没反馈长度,貌似没有高效可言!只能再变回去求长度等等方法

import pickle
print(pickle.dumps(123))
with open("pickle1.dat","wb+") as p:
    pickle.dump(123,p)
    p.seek(0)
    print(t:=pickle.loads(p.read()),type(t))
#
b'\x80\x04K{.'
123 <class 'int'>

之前看到一个写法,不生成磁盘文件,但是可以模拟生成一个文件,StringIO和BytesIO,但是运行失败!
那么会看循序写法

import pickle
f=b'\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03123\x94.\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03456\x94.'
for i in range(len(f)):
    try:
        j=pickle.loads(f[i:])
    except:pass
    else:print(i,j,type(j),"\n",f[i:])
#
0 123 <class 'str'> 
 b'\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03123\x94.\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03456\x94.'
2 123 <class 'str'> 
 b'\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03123\x94.\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03456\x94.'
11 123 <class 'str'> 
 b'\x8c\x03123\x94.\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03456\x94.'
18 456 <class 'str'> 
 b'\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03456\x94.'
20 456 <class 'str'> 
 b'\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03456\x94.'
29 456 <class 'str'> 
 b'\x8c\x03456\x94.'

52.9 µs
这里一个dump可以被解析出三次,不行!

如果按照再次执行pickle呢!?

%%timeit
import pickle
f=b'\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03123\x94.\x80\x04\x95\x07\x00\x00\x00\x00\x00\x00\x00\x8c\x03456\x94.'
i=0
l=len(f)
while 1:
    j=pickle.loads(f[i:])
    print(j)
    lj=len(pickle.dumps(j))
    i+=lj
    if i==l:break

3.35 µs
我并不认为这是个合理的做法,我认为他应该能返回一个长度!

使用Struct模块读/写二进制文件

在Python中,Struct模块访问二进制文件的方法如下:
(1)向二进制文件中写数据
先使用pack()函数对数据对象按照指定格式进行格式化,然后使用文件对象的writer()函数将序列化的结果写入二进制文件中

struct.pack(fmt,data1,data2,…)

fmt为给定的格式化字符串,data1、data2为要写入的数据对象

(2)从二进制文件中读取数据
先使用文件对象的read()函数读取二进制文件中的内容,然后使用Struct模块的unpack()函数完成反序列化后得到原来的数据对象

struct.unpack(fmt,string)

string为要反序列化的字符串

import struct
a="Hello"
b=35
c=89.46
binary1=struct.pack('5sif',a.encode("UTF-8"),b,c)
with open("struct_file.dat","wb") as struct_file:
    struct_file.write(binary1)
    print("写入数据成功!")
print(binary1)
#
b'Hello\x00\x00\x00#\x00\x00\x00\x85\xeb\xb2B'

由此可看,struct的功能就是将数据转化成特定格式的字节并连接在一起。

5s:5个长度的字符串; i:int, f:float
就是这步很迷,而这里必须用字符串做参数吗?

s='5sif'
binary1=struct.pack(s,a.encode("UTF-8"),b,c)

同样效果,如果不行的话,就真的有要用exec了,那么之前用过exec的,是否也可以这样处理呢,总之又添加了个思路!

a=10
l=list(range(a))
b=3
l[b]

这都行!!!

import struct
with open("struct_file.dat","rb") as struct_file:
    binary1=struct_file.read()
    a,b,c=struct.unpack("5sif",binary1)
    print("a:",a)
    print("a:",a.decode("UTF-8"))
    print("b:",b)
    print("c:",c)
#
a: b'Hello'
a: Hello
b: 35
c: 89.45999908447266

struct在打包和解包的时候,需要格式化的参数,这个决定了打包内容是否完整,解包规格是否对应。

如果是在远端客户端,没有对应的参数,是无法获得内容的,如果在近段,如果仅能打开python,如果拿到了binary,可以尝试暴力解析。

使用Marshal模块读/写二进制文件

marshal.dump(data,file)

marshal.load(data.file)

marshal: 排列

import marshal
import pickle
data1=['def',34.87]
data2={1:"优秀",2:"良好",3:"合格",4:"不合格"}
data3=(5,6,7)
with open("marshal_file.dat","wb+") as output_file:
    marshal.dump(data1,output_file)
    marshal.dump(data2,output_file)
    marshal.dump(data3,output_file)
    output_file.seek(0)
    print("marshal:",output_file.read())
with open("pickle1.dat","wb+") as p:
    pickle.dump(data1,p)
    pickle.dump(data2,p)
    pickle.dump(data3,p)
    p.seek(0)
    print("pickle:",p.read())
#
marshal: b'\xdb\x02\x00\x00\x00\xda\x03def\xe7\x8f\xc2\xf5(\\oA@\xfb\xe9\x01\x00\x00\x00\xf5\x06\x00\x00\x00\xe4\xbc\x98\xe7\xa7\x80\xe9\x02\x00\x00\x00\xf5\x06\x00\x00\x00\xe8\x89\xaf\xe5\xa5\xbd\xe9\x03\x00\x00\x00\xf5\x06\x00\x00\x00\xe5\x90\x88\xe6\xa0\xbc\xe9\x04\x00\x00\x00\xf5\t\x00\x00\x00\xe4\xb8\x8d\xe5\x90\x88\xe6\xa0\xbc0\xa9\x03\xe9\x05\x00\x00\x00\xe9\x06\x00\x00\x00\xe9\x07\x00\x00\x00'
pickle: b'\x80\x04\x95\x14\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x03def\x94G@Ao\\(\xf5\xc2\x8fe.\x80\x04\x954\x00\x00\x00\x00\x00\x00\x00}\x94(K\x01\x8c\x06\xe4\xbc\x98\xe7\xa7\x80\x94K\x02\x8c\x06\xe8\x89\xaf\xe5\xa5\xbd\x94K\x03\x8c\x06\xe5\x90\x88\xe6\xa0\xbc\x94K\x04\x8c\t\xe4\xb8\x8d\xe5\x90\x88\xe6\xa0\xbc\x94u.\x80\x04\x95\t\x00\x00\x00\x00\x00\x00\x00K\x05K\x06K\x07\x87\x94.'

虽然marshal、pickle的用法一样,但是数据转换格式、生成内容不一样!

import marshal
with open("marshal_file.dat","rb") as marshal_file:
    data=marshal_file.read()
    print(data)
    marshal_file.seek(0)
    print(marshal.load(marshal_file))
    print(marshal.load(marshal_file))
    print(marshal.load(marshal_file))
    marshal_file.seek(0)
    print(marshal.loads(data))

Python标准库—pickle/marshal模块

marshal与pickle的区别
marshal不能被用于序列化用户定义类及其实例。pickle 能够透明地存储并保存类实例,然而此时类定义必须能够从与被存储时相同的模块被引入。
同样用于序列化的marshal格式不保证数据能移植到不同的 Python 版本中(即marshal不保证能跨版本)。因为它的主要任务是支持 .pyc文件,必要时会以破坏向后兼容的方式更改这种序列化格式,为此 Python 的实现者保留了更改格式的权利。pickle序列化格式可以在不同版本的 Python 中实现向后兼容,前提是选择了合适的 pickle 协议。

写入测试:
marshal: 401 µs
pickle: 396 µs
差不太多

上面是wb+,但一旦修改为wb… 差不多,当依赖于外部存储的时候,耗时就不稳定了,虽然我是读写内存中的虚拟磁盘

使用Shelve模块读/写二进制文件

Shelve模块提供了一种类似字典的方式操作二进制文件的功能,如向二进制文件中写入数据、读出数据或修改数据等。
Shelve提供了一个简单的数据存储方案,只有一个open()函数。
open()函数接受一个文件名作为参数,返回一个shelf对象

shelve.open(filename,mode,protocol=None,writeback=False)

filename: 打开或创建的二进制文件
mode: 文件打开模式
(1)‘c’:如果文件不存在则创建,允许读/写
(2)‘r’:只读
(3)‘w’:可读/写
(4)‘n’:每次调用open()函数都重新创建一个空文件,可读/写
protocol:序列化模式,可以是1或2,默认值为None
writeback:是否将所有从二进制文件中读取的对象存放到一个内存缓存,默认为False

写入数据

import shelve
tg=dict(zip(["name","age"],["Tong Gang",31]))
tj=dict(zip(['name','age'],['Tie Jin',42]))
with shelve.open('shelve_file') as shelve_file:
    shelve_file['tg']=tg
    shelve_file['tj']=tj
    print("写入数据成功!")

执行后会生成三个文件:shelve_file.dir、shelve_file.dat、shelve_file.bak

import os
print("shelve_file.dir创建时间:",os.path.getctime("shelve_file.dir"))
print("shelve_file.dat创建时间:",os.path.getctime("shelve_file.dat"))
print("shelve_file.bak创建时间:",os.path.getctime("shelve_file.bak"))
#
shelve_file.dir创建时间: 1604541547.2176278
shelve_file.dat创建时间: 1604541547.2156308
shelve_file.bak创建时间: 1604541547.2176278

这个顺序是文件浏览器的时间排序倒序,我突然想到,莫非时间是精确到… 果然
但是windows得时间排序只精确到秒,然后一秒内的排序是按照文件名排的,结果无论是时间正排序还是倒序,都是bak、dat、dir的顺序!

而看到更精确的时间,确定创建顺序是dat、dir和bak

dat是字典内容,dir是字典名和索引位置,bak是dir的备份!

不对劲,这里,每次写入,不会检查dat之前的内容,对dat是追加操作,对dir是覆盖操作,有些问题,还是说我方法错误?

PS:之后我再运行这段代码,并没有出现上述dat狂加的状况

总之一串操作后,dat内容是不断增加,而dir的数量符合预计

import shelve
tg=dict(zip(["name","age"],["Tong Gang",31]))
tj=dict(zip(['name','age'],['Tie Jin',42]))
with shelve.open('shelve_file',"w") as shelve_file:
    shelve_file['tg']=tg
    shelve_file['tj']=tj
    del(shelve_file['tg'])

除非用"n"标识

Python编程——shelve模块的使用详解(附实例)

这篇文章用,我第一次看到了字典的预设,于是看了下文档

help(dict.get)
get(self, key, default=None, /)
    Return the value for key if key is in the dictionary, else default.

读出内容

import shelve
with shelve.open('shelve_file') as shelve_file:
    print(shelve_file['tj'])
    print(shelve_file.get('tj'))

修改内容

import shelve
with shelve.open('shelve_file') as shelve_file:
    tg=shelve_file['tg']
    tg['name']="TT"
    shelve_file['tg']=tg
    print(shelve_file['tg'])
#
{'name': 'TT', 'age': 31}
    shelve_file['tg']['name']="GG"
    print(shelve_file['tg'])
# 未变
{'name': 'TT', 'age': 31}

对元素中的元素进行追加,需要提出来,修改后,再写入!

一切都了解了,蛋疼的就是del没有删除dat的内容

import time
print(a:=time.time())
with open("e:\\test.mp4","ab+") as t:
    print((b:=time.time())-a)
    t.seek(0)
    t1=t.read()
    print((c:=time.time())-b)
    t.write(t1)
    print((d:=time.time())-c)
#
1604552972.4121177
0.0009992122650146484
0.6541306972503662
2.5937156677246094
with open("A://2017-11-12 19-39-16 创建的截图.png","ab+") as t:
    t.seek(int(t.tell()/2))
    t.truncate()

原来truncate就是文件截断呀!

典型案例

合并文件

合并指定路径下的联系人文件,要求没有重复的联系人信息
分析:再指定路径下有3个联系人文件contacts01.txt、contacts02.txt、contacts03.txt.内容如下

contacts01.txt
姓名,电话,QQ
赵大,13100000001,100001
钱大,13200000001,200001
赵三,13100000003,100003

contacts02.txt
姓名,电话,QQ
钱大,13200000001,200001
钱二,13200000002,200002
钱三,13200000003,200003

contacts03.txt
姓名,电话,QQ
钱二,13200000002,200002
钱三,13200000003,200003
孙三,13300000003,300003

三个联系人文件中的联系人信息有重复之处,需要合并
(1)获取并创建指定路径下的联系人文件列表fileList
(2)创建保存联系人的联系人信息列表tempList和联系人信息文件contacts.txt
(3)对fileList中的文件file进行遍历;如果file中指定行的联系人信息不在tempList中,则说明联系人信息没有重复,添加到tempList中,写入文件contacts.txt中;否则,不添加到tempList中,也不写入文件contacts.txt中
(4)如果发生异常,则进行处理,并给出提示信息

import os,sys,re
try:
    #获取目标文件夹的路径
    source_file_dir=os.getcwd()+'\\SourceFile'
    #获取当前文件夹中的文件名称列表
    fileList=os.listdir(source_file_dir)
    #如果指定路径下没有需要合并的联系人文件,则退出程序
    if not fileList:
        print("指定路径下没有需要合并的联系人文件。")
        sys.exit(0) #退出程序
    #打开当前目录下的contacts.txt文件,如果没有则创建
    with open(source_file_dir+"\\contacts.txt","w") as file:
        tempList=[]  #创建联系人信息列表
        #遍历文件列表中的联系人文件
        for fileName in fileList:
            filepath=source_file_dir+"\\"+fileName
            #遍历单个文件,读取一行
            for line in open(filepath):
                line=re.sub('\n','',line)  #删除联系人信息末尾的'\n'
                #如果再line中而不在tempList中,则line没有重复,添加到tempList,并写入file中
                if line not in tempList and line+'\n' not in tempList:
                    tempList.append(line)
                    file.write(line)
                    file.write('\n')
    print("合并联系人文件成功!")
except IOError as e:  #指定路径不存在或读/写异常
    print("异常信息:",e.args[1],e.filename)
except:
    print("合并联系人文件失败")
#
异常信息: 系统找不到指定的路径。 F:\2019\Documents\python\SourceFile
#
合并联系人文件成功!

SourceFile\\contacts.txt
姓名,电话,QQ
赵大,13100000001,100001
钱大,13200000001,200001
赵三,13100000003,100003
钱二,13200000002,200002
钱三,13200000003,200003
孙三,13300000003,300003

第一次看到except的高阶使用,之前看到过介绍,却没有实际执行过!

s.strip("\n")
# 129 ns
re.sub("\n","",s)
# 1.04 µs

CSV文件操作

CSV(Comma Separated Values,逗号分隔值)文件是一种常用的文本格式文件,用以存储表格数据,包括数字、字符等。很多程序再处理数据时都会使用CSV文件,其使用非常广泛。
Python内置了Csv模块来处理CSV文件。csvFile为打开的CSV文件对象。
(1)csv.writer(csvfile):返回一个writer对象。csvFile为打开的CSV文件对象
(2)writer.writerow(row):向CSV文件中写入一行数据
(3)writer.writerows(rows):向CSV文件中一次写入多行数据
(4)csv.reader(csvFile):返回一个reader对象,可通过迭代读取其中的内容。csvFile为打开的CSV文件对象

import csv
#标题
headers=['NO','Name','Sex','Age','Salary']
#多行数据
rows=[('1001','Jack','male',28,12800),('1002','Rose','female',22,8800),('1003','Tim','male','26',10800)]
#向CSV文件中写数据
try:
    #以些模式打开文件
    with open('salary.csv','w',newline='') as file:
        fw=csv.writer(file)
        fw.writerow(headers)
        fw.writerows(rows)
        print('写入数据成功!')
except:
    print('写入数据失败!')
#从CSV文件中读数据
print('读取数据结果:')
try:
    with open('salary.csv','r') as file:
        fr=csv.reader(file)
        rows=[row for row in fr]
        print('标题:',rows[0])
        print('第一行数据:',rows[1])
        column=[row[1] for row in rows]
        print('第一列数据:',column)
        print('第一行第一列数据:',rows[1][1])
except:
    print('读取数据失败!')
#
写入数据成功!
读取数据结果:
标题: ['NO', 'Name', 'Sex', 'Age', 'Salary']
第一行数据: ['1001', 'Jack', 'male', '28', '12800']
第一列数据: ['Name', 'Jack', 'Rose', 'Tim']
第一行第一列数据: Jack

从这一章开始到现在的一个转变,以写入数据成功变为,except写入数据失败,之前我看到写入数据成功时,总觉得不对劲,现在连接上了,但也不排除会两句话同时出现,不过关于写法详细的想法:
1、递进知识
2、如果一开始就觉得不对劲,说明善于思考,但是一直觉得不对劲,就是不善于预习!

除了可以使用以上提供的方法读取CSV文件中的数据,NumPy和Pandas提供了能快速读取CSV文件中数据的函数。

你可能感兴趣的:(啃书,Python,程序设计,从入门到实战应用)