当前位置:   article > 正文

Python入门(二十四)-文件操作2_writelines和write的运行

writelines和write的运行

二十四、文件操作2

24.1 写入文件(输出内容)

使用r+、w、w+、a、a+模式打开文件,可以写入,其中,r+、w、w+模式文件指针位于文件开头,a、a+指针位于结尾。文件指针的含义类似于我们使用word等文本编辑器时,光标的位置,我们可以在此位置读取或写入数据。

24.1.1 文件指针操作tell()和seek()

实现对文件指针的移动,文件对象提供了 tell() 函数和 seek() 函数。tell() 函数用于判断文件指针当前所处的位置,而 seek() 函数用于移动文件指针到文件的指定位置。

注意:
当程序使用文件对象读写数据时,文件指针会自动向后移动,读写了多少个数据,文件指针就自动下那个后移动多少位置。
当向文件中写入数据时,如果不是文件的尾部,写入位置的原有数据不会自行向后移动,新写入的数据会将文件中处于该位置的数据直接覆盖掉。

两个函数的语法都很简单,语法格式:

file.tell()
file.seek(offset[,whence])
- file:表示文件对象;
- whence:作为可选参数,用于指定文件指针要放置的位置,该参数的参数值有 3 个选择:0 代表文件头(默认值)、1 代表当前位置、2 代表文件尾。
- offset:表示相对于 whence 位置文件指针的偏移量,正数表示向后偏移,负数表示向前偏移。例如,当whence == 0 &&offset == 3(即 seek(3,0) ),表示文件指针移动至距离文件开头处 3 个字符的位置;当whence == 1 &&offset == 5(即 seek(5,1) ),表示文件指针向后移动,移动至距离当前位置 5 个字符处。
  • 1
  • 2
  • 3
  • 4
  • 5

注意:
使用seek()函数时,当 offset 值非 0 时,Python 要求文件必须要以二进制格式打开(b模式),否则会抛出 io.UnsupportedOperation 错误。
不指定whence时,代表从开始位置移动指针

示例:

>>> f = open('悯农.txt','rb')
>>> f.tell()
0
>>> #读取一个字节
>>> f.read(1)
b'\xc3'
>>> #看看现在指针位置,读取后指针自动移动一位
>>> f.tell()
1
>>> # 指针向后移动3个字节
>>> f.seek(3)
3
>>> f.tell()
3
>>> f.seek(10)
10
>>> f.tell()
10
>>> #从当前位置移动指针
>>> f.seek(5,1)
15
# 使用字符格式打开文件
>>> f= open('悯农.txt','r')
>>> f.tell()
0
#读取5个字符
>>> f.read(5)
'悯农\n唐.'
#指针移动了9位
>>> f.tell()
9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

24.1.2 输出内容write()和writelines()

前面章节中学习了如何使用 read()、readline() 和 readlines() 这 3 个函数读取文件,如果我们想把一些数据保存到文件中,又该如何实现呢?

Python 中的文件对象提供了 write() 函数和writelines()函数,可以向文件中写入指定内容。该函数的语法格式如下:

file.write(string)
file.writelines(迭代对象)
  • 1
  • 2

其中,file 表示已经打开的文件对象;string 表示要写入文件的字符串(或字节串,仅适用写入二进制文件中)。
注意,在使用 write()或writelines() 向文件中写入数据,需保证使用 open() 函数是以 r+、w、w+、a 或 a+ 的模式打开文件,否则执行 write() 函数会抛出 io.UnsupportedOperation 错误。
前面已经讲过,如果打开文件模式中包含 w(写入),那么向文件中写入内容时,会先清空原文件中的内容,然后再写入新的内容。
而如果打开文件模式中包含 a(追加),则不会清空原有内容,而是将新写入的内容会添加到原内容后边。示例:

>>> import os
>>> f = open('春望.txt','w')
>>> f.write('春望')
2
>>> f.write('\n唐.杜甫\n')
6
>>> #必须使用close()函数书写的内容才能更新进去
>>> f.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述

注意:
在写入文件完成后,一定要调用 close() 函数将打开的文件关闭,否则写入的内容不会保存到文件中。例如,将上面程序中最后一行 f.close() 删掉,再次运行此程序并打开 a.txt,你会发现该文件是空的。这是因为,当我们在写入文件内容时,操作系统不会立刻把数据写入磁盘,而是先缓存起来,只有调用 close() 函数时,操作系统才会保证把没有写入的数据全部写入磁盘文件中。
我们再次使用w、w+模式打开文件后,文本内容会被清空

>>> f = open('春望.txt','w+')
>>> #如果现在打开文本文件,里面是空的
>>> #我们使用writelines()重新写入
>>> f.writelines(('春望\n'。'))
... '国破山河在,城春草木深。'))
... '国破山河在,城春草木深。'))
>>> #可以使用文件对象提供的flush()函数更新数据
>>> f.flush()
>>> #不加换行符是没有换行的,现在文本文件中就在一行,尽管我在命令行输入了回车
>>> f.close()
>>> f = open('春望.txt','a+')
>>> #使用追加模式添加数据
>>> f.write('\n烽火连三月,家书抵万金。')
13
>>> f.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

现在里面的内容是这样的:
在这里插入图片描述

采用不同的文件打开模式,会直接影响 write() 函数向文件中写入数据的效果
如果向文件写入数据后,不想马上关闭文件,也可以调用文件对象提供的 flush() 函数,它可以实现将缓冲区的数据写入文件中。

有读者可能会想到,通过设置 open() 函数的 buffering 参数可以关闭缓冲区,这样数据不就可以直接写入文件中了?对于以二进制格式打开的文件,可以不使用缓冲区,写入的数据会直接进入磁盘文件;但对于以文本格式打开的文件,必须使用缓冲区,否则 Python 解释器会 ValueError 错误。例如:

f = open("春望.txt", 'w',buffering = 0)
f.write("写入一行新数据")
运行结果为:
Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\demo.py", line 1, in <module>
    f = open("a.txt", 'w',buffering = 0)
ValueError: can't have unbuffered text I/O
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

例如,还是以 《春望.txt 》文件为例,通过使用 writelines() 函数,可以轻松实现将文件中的数据复制到其它文件中,实现代码如下:

f = open('春望.txt', 'r')
n = open('春望2.txt','w+')
n.writelines(f.readlines())
n.close()
f.close()
  • 1
  • 2
  • 3
  • 4
  • 5

执行此代码,在 春望.txt 文件同级目录下会生成一个 春望2.txt 文件,且该文件中包含的数据和 春望.txt 完全一样。

需要注意的是,使用 writelines() 函数向文件中写入多行数据时,不会自动给各行添加换行符。上面例子中,之所以 b.txt 文件中会逐行显示数据,是因为 readlines() 函数在读取各行数据时,读入了行尾的换行符。

24.2 上下文管理with语句

任何一门编程语言中,文件的输入输出、数据库的连接断开等,都是很常见的资源管理操作。但资源都是有限的,在写程序时,必须保证这些资源在使用过后得到释放,不然就容易造成资源泄露,轻者使得系统处理缓慢,严重时会使系统崩溃。

例如,前面在介绍文件操作时,一直强调打开的文件最后一定要关闭,否则会程序的运行造成意想不到的隐患。但是,即便使用 close() 做好了关闭文件的操作,如果在打开文件或文件操作过程中抛出了异常,还是无法及时关闭文件。

为了更好地避免此类问题,不同的编程语言都引入了不同的机制。在 Python 中,对应的解决方式是使用 with as 语句操作上下文管理器(context manager),它能够帮助我们自动分配并且释放资源。
简单的理解,同时包含 enter() 和 exit() 方法的对象就是上下文管理器。常见构建上下文管理器的方式有 2 种,分别是基于类实现和基于生成器实现。
例如,使用 with as 操作已经打开的文件对象(本身就是上下文管理器),无论期间是否抛出异常,都能保证 with as 语句执行完毕后自动关闭已经打开的文件。

首先学习如何使用 with as 语句。with as 语句的基本语法格式为:

with 表达式 [as target]:
    代码块
  • 1
  • 2

此格式中,用 [] 括起来的部分可以使用,也可以省略。其中,target 参数用于指定一个变量,该语句会将 expression 指定的结果保存到该变量中。with as 语句中的代码块如果不想执行任何语句,可以直接使用 pass 语句代替。

>>> import fileinput
>>> # 使用with语句打开文件,f该语句负责关闭文件
>>> with fileinput.input(files=('春望.txt','悯农.txt'))as f:
...     for line in f:
...         print(line)
...
春望

国破山河在,城春草木深。国破山河在,城春草木深。

烽火连三月,家书抵万金。
悯农

唐.李绅

春种一粒粟,秋收万颗子。

四海无闲田,农夫犹饿死。

锄禾日当午,汗滴禾下土。

谁知盘中餐,粒粒皆辛苦。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

with as 语句实现的底层原理到底是怎样的呢?其实很简单,使用with语句管理的资源必须是一个实现上下文管理协议(context manage protocol)的类,这个类的对象可被称为上下文管理器。要实现上下文管理器,必须包含以下两个方法:

  • context_manager.__enter–():进入上下文管理器自动调用的方法。在with之前执行,返回值被赋值给as后的变量,也可以返回多个值,赋值给as后使用元组组成的多个变量。
  • context_manager.__exit–(exc_type,exc_value,exc_traceback):退出上下文管理器自动调用的方法。在with代码执行完后执行,如果with成功执行,该方法的三个参数为None,如果执行异常,也调用该方法,使用sys.exc_info得到的异常信息将作为调用该方法的参数。

因此,只要一个类实现了__enter–()与__exit–()方法,就可以使用with语句管理它。通过__exit–()的参数,即可判断是否出现异常。
文件对象、FIleInput对象都实现了这两种方法,因此可以使用with管理。
下面我们自定义一个实现上下文管理协议的类,并使用with语句管理它。

class test_class:
    def __init__(self,tag):
        self.tag = tag
        print('构造器初始化资源:%s'%tag)
    #定义__enter__方法,在with代码块之前执行
    def __enter__(self):
        print('[__enter__ %s]:'%self.tag)
        #返回值将作为as子句后变量的值
        return 'as_object' #可以是任意类型的值,打开文件返回的是内容
    # 定义__exit__方法,with代码块之后执行
    def __exit__(self,exc_type,exc_value,exc_traceback):
        print('[__exit__ %s]:'%self.tag)
        #exc_traceback为None,代表没异常
        if exc_traceback is None:
            print('没有异常关闭资源')
        else:
            print('遇到异常关闭资源')
            return False #可以省略,默认返回None,也被看做是False
with test_class('孙悟空' as test1:
    print(test1)
    print('[with代码块]没有异常')
print('---------------------------')
with test_class('白骨精') as test2:
    print('[with代码块]异常之前的代码')
    raise Exception
    print('[with代码块]~~~~~~~~~~异常之后的代码')
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

我们使用了两次with语句,第一次无异常,第二次抛出异常,通过输出结果对比可以看出with管理差异。

构造器初始化资源:孙悟空--------------1
[__enter__ 孙悟空]:    -------------------2
as_object      -----------------3
[with代码块]没有异常
[__exit__ 孙悟空]:       ----------------4
没有异常关闭资源
-------------------------------------
构造器初始化资源:白骨精--------------1
[__enter__ 白骨精]:    -------------------2
[with代码块]异常之前的代码  -----------------3
[__exit__ 孙悟空]:      ----------------4 
遇到异常关闭资源
Traceback(most recent call last):
    ....
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可以看到程序执行顺序:

  1. 类对象初始化,
  2. 执行enter方法
  3. 执行with代码块,as后面的变量是enter方法的返回值,遇到异常中断执行with代码块,直接执行exit方法
  4. 执行exit方法

24.3 os模块的文件和目录函数

24.3.1 目录相关函数

与目录相关的函数如下:

  • os.getcwd():获取当前工作目录
  • os.chdir(path):改变当前工作目录
  • os.fchdir(fd):通过文件描述符改变当前目录。
  • os.chroot(path):改变当前进程的根目录
  • os.listdir(path):返回path对应目录下的所有文件和子目录。
  • os.mkdir(path[,mode]): 创建path对应的目录,mode用于指定该目录的权限,Unix风格的权限,读权限r=4,写权限w=2,执行权限x=1 。详细介绍可以参照以前的Linux笔记,例如0o743,代表所有者可读写执行,组用户可读,其他用户可写可执行。
  • os.mkdirs(path[,mode]):作用类似os.mkdir(),而且可以递归创建多层目录。
  • os.rmdir(path):删除path对应的空目录。如果目录非空,报错OSError,可以先使用os.remove()删除文件
  • os.removedirs(path):递归删除目录
  • os.rename(scr,dst):重命名文件或目录,将src重命名为dst
  • os.renames(old,new):递归重命名。例如:将a/b/c重命名为1/2/3,从最内层文件开始
    这部分内容与Linux的文件操作命令类似,可以参看我以前写的笔记。

24.3.2 权限相关函数

与权限相关的函数如下:

  • os.access(path,mode):检查path对应的文件或目录是否既有指定权限,其中mode可选如下一种或几种参数(使用管道命令符连接):
import os
ret = os.access(path,mode)
- os.F_OK:判断是否存在;
- os.R_OK:判断是否可读;
- os.W_OK:判断是否可写;
- os.X_OK:判断是否可执行;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

示例:

import os,sys
#判断当前目录权限
ret = os.access('.',os.F_OK|os.R_OK|os.W_OK|os.X_OK)
prinnt(ret)    #Ture

  • 1
  • 2
  • 3
  • 4
  • 5
  • os.chmod(path,mode):更改权限
mode支持的权限定义:
stat.S_IXOTH:其他用户有执行权限。
stat.S_IWOTH:其他用户有写权限。
stat.S_IROTH:其他用户有读权限。
stat.S_IRWXO:其他用户有全部权限。
stat.S_IXGRP:组用户有执行权限。
stat.S_IWGRP:组用户有写权限。
stat.S_IRGRP:组用户有读权限。
stat.S_IRWXG:组用户有全部权限。
stat.S_IXUSR:所有者有执行权限。
stat.S_IWUSR:所有者有写权限。
stat.S_IRUSR:所有者有读权限。
stat.S_IRWXU:所有者有全部权限。
stat.S_IREAD:Windows将该文件设为只读的
stat.S_IWRITE:Windows将该文件设为可写的
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • os.chown(path,uid,gid):更改文件所有者,uid为用户id,gid为组id,主要在UNIX文件系统下有效
  • os.fchmod(fd,mode):改变文件访问权限,该文件由文件描述符fd指定,
  • os.fchown(fd,uid,gid):使用文件描述符改变文件所有者

24.3.3 文件访问相关函数

与文件访问相关函数如下:

  • os.open(file,flags[,mode]):打开一个文件,并设置打开选项,mode参数可选,返回文件描述符(不是文件对象)
flags代表打开文件的旗标,支持如下一个或多个选项:
- os.O_RDONLY:以只读方式打开
- os.O_WRONLY:以只写方式打开
- os.O_RDWR:以读写方式打开
- os.O_NONBLOCK:打开时不阻塞
- os.O_APPEND:以最佳方式打开
- os.O_CREAT:创建并打开一个新文件
- os.O_TRUNC:打开一个文件并截断它的长度为0(必须有写权限)
- os.O_EXCL:在创建文件时,如果指定的文件存在,则返回错误
- os.O_SHLOCK:自动获取共享锁
- os.O_EXLOCK:自动获取独立锁
- os.O_DIRECT:消除或减少缓存效果
- os.O_FSYNC:同步写入
- os.O_NOFOLLOW:不追踪软连接
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • os.read(fd,n):从文件描述符fd中读取最多n个字节,返回读到的字节串。如果文件描述符fd对应的文件已到达结尾,则返回一个空字节串。
  • os.write(fd,str):将字节串写入文件描述符fd中,返回实际写入的字节串长度
  • os.close(fd):关闭文件描述方法fd
  • os.lseek(fd,pos,how):该函数同样用于移动文件指针。其中how参数指定从哪里开始移动。0或SEEK_SET从文件头开始,1或SEEK_CUR从当前位置开始,2或SEEK_END从结尾开始。
>>> f = os.open('行路难.txt',os.O_RDWR|os.O_CREAT)
>>> #写入文件
>>> len1 = os.write(f,'行路难\n'.encode('utf-8'))
>>> len2 = os.write(f,'唐.李白\n'.encode('utf-8'))
>>> #将文件指针移动到开始处
>>> os.lseek(f,0,os.SEEK_SET)
0
>>> #读取文件内容
>>> data = os.read(f,len1+len2)
>>> print(data)
b'\xe8\xa1\x8c\xe8\xb7\xaf\xe9\x9a\xbe\n\xe5\x94\x90.\xe6\x9d\x8e\xe7\x99\xbd'
>>> print(data.decode('utf-8'))
行路难
唐.李白
>>> f.tell()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'tell'
>>> os.lseek(f,0,SEEK_END)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'SEEK_END' is not defined
>>> os.lseek(f,0,os.SEEK_END)
23
>>> # 强迫症发作,写完这首诗
>>> os.write(f,'金樽清酒斗十千,玉盘珍馐直万钱。\n停杯投箸不能食,拔剑四顾心茫 然。\n欲渡黄河冰塞封,将登太行雪满山。\n闲来垂钓碧溪上,忽复乘周梦日边。\n行路 难!行路难!多歧路,今安在?\n长风破浪会有时,直挂云帆济沧海。'.encode('utf-8'))
296
>>> os.close(f)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

需要注意的是,这里的文件描述符已经没有tell()方法了。

  • os.fdopen(fd[,mode[,bufsize]]):通过文件描述符打开文件,并返回对应的文件对象。
  • os.closerange(fd_low,fd_high):关闭从fd_low(包含)到 fd_high(不包含)范围的所有文件描述符
  • os.dup(fd):复制文件描述符
  • os.dup2(fd,fd2):将文件描述方法fd复制到另一个文件描述符fd2中
  • os.fturncate(fd,length):将fd对应的文件截取到length长度,因此此处传入的length参数不应该超过文件大小。
  • os.remove(path):删除path对应的文件,如果path是一个文件夹,则抛出OSError错误。如果要删除目录使用os.rmdir()
  • os.link(src,dst):创建从src到dst的符号链接,对应Windows的快捷方式

24.4 临时文件及tempfile模块

tempfile 模块专门用于创建临时文件和临时目录,它既可以在 UNIX 平台上运行良好,也可以在 Windows 平台上运行良好。

tempfile 模块中常用的函数:

tempfile 模块函数功能描述
tempfile.TemporaryFile(mode=‘w+b’, buffering=None, encoding=None, newline=None, suffix=None, prefix=None, dir=None)创建临时文件。该函数返回一个类文件对象,也就是支持文件 I/O。
tempfile.NamedTemporaryFile(mode=‘w+b’, buffering=None, encoding=None, newline=None, suffix=None, prefix=None, dir=None, delete=True)创建临时文件。该函数的功能与上一个函数的功能大致相同,只是它生成的临时文件在文件系统中有文件名。
tempfile.SpooledTemporaryFile(max_size=0, mode=‘w+b’, buffering=None, encoding=None, newline=None, suffix=None, prefix=None, dir=None)创建临时文件。与 TemporaryFile 函数相比,当程序向该临时文件输出数据时,会先输出到内存中,直到超过 max_size 才会真正输出到物理磁盘中。
tempfile.TemporaryDirectory(suffix=None, prefix=None, dir=None)生成临时目录。
tempfile.gettempdir()获取系统的临时目录。
tempfile.gettempdirb()与 gettempdir() 相同,只是该函数返回字节串。
tempfile.gettempprefix()返回用于生成临时文件的前缀名。
tempfile.gettempprefixb()与 gettempprefix() 相同,只是该函数返回字节串。
提示:表中有些函数包含很多参数,但这些参数都具有自己的默认值,因此如果没有特殊要求,可以不对其传参。

tempfile 模块还提供了 tempfile.mkstemp() 和 tempfile.mkdtemp() 两个低级别的函数。上面介绍的 4 个用于创建临时文件和临时目录的函数都是高级别的函数,高级别的函数支持自动清理,而且可以与 with 语句一起使用,而这两个低级别的函数则不支持,因此一般推荐使用高级别的函数来创建临时文件和临时目录。

此外,tempfile 模块还提供了 tempfile.tempdir 属性,通过对该属性赋值可以改变系统的临时目录。

下面程序示范了如何使用临时文件和临时目录:

import tempfile
# 创建临时文件
fp = tempfile.TemporaryFile()
print(fp.name)
fp.write('两情若是久长时,'.encode('utf-8'))
fp.write('又岂在朝朝暮暮。'.encode('utf-8'))
# 将文件指针移到开始处,准备读取文件
fp.seek(0)
print(fp.read().decode('utf-8')) # 输出刚才写入的内容
# 关闭文件,该文件将会被自动删除
fp.close()
# 通过with语句创建临时文件,with会自动关闭临时文件
with tempfile.TemporaryFile() as fp:
    # 写入内容
    fp.write(b'I Love Python!')
    # 将文件指针移到开始处,准备读取文件
    fp.seek(0)
    # 读取文件内容
    print(fp.read()) # b'I Love Python!'
# 通过with语句创建临时目录
with tempfile.TemporaryDirectory() as tmpdirname:
    print('创建临时目录', tmpdirname)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
'
运行

上面程序以两种方式来创建临时文件:
第一种方式是手动创建临时文件,读写临时文件后需要主动关闭它,当程序关闭该临时文件时,该文件会被自动删除。
第二种方式则是使用 with 语句创建临时文件,这样 with 语句会自动关闭临时文件。

上面程序最后还创建了临时目录。由于程序使用 with 语句来管理临时目录,因此程序也会自动删除该临时目录。

运行上面程序,可以看到如下输出结果:

C:\Users\admin\AppData\Local\Temp\tmphvehw9z1
两情若是久长时,又岂在朝朝暮暮。
b'I Love Python!'
创建临时目录C:\Users\admin\AppData\Local\Temp\tmp3sjbnwob
  • 1
  • 2
  • 3
  • 4

上面第一行输出结果就是程序生成的临时文件的文件名,最后一行输出结果就是程序生成的临时目录的目录名。需要注意的是,不要去找临时文件或临时文件夹,因为程序退出时该临时文件和临时文件夹都会被删除。

24.5 对象转存及pickle模块

Python 中有个序列化过程叫作 pickle,它能够实现任意对象与文本之间的相互转化,也可以实现任意对象与二进制之间的相互转化。也就是说,pickle 可以实现 Python 对象的存储及恢复。

值得一提的是,pickle 是 python 语言的一个标准模块,安装 python 的同时就已经安装了 pickle 库,因此它不需要再单独安装,使用 import 将其导入到程序中,就可以直接使用。

pickle 模块提供了以下 4 个函数供我们使用:

  • dumps():将 Python 中的对象序列化成二进制对象,并返回;
  • loads():读取给定的二进制对象数据,并将其转换为 Python 对象;
  • dump():将 Python 中的对象序列化成二进制对象,并写入文件;
  • load():读取指定的序列化数据文件,并返回对象。

以上这 4 个函数可以分成两类,其中 dumps 和 loads 实现基于内存的 Python 对象与二进制互转;dump 和 load 实现基于文件的 Python 对象与二进制互转。

  1. pickle.dumps()函数
    此函数用于将 Python 对象转为二进制对象,其语法格式如下:
dumps(obj, protocol=None, *, fix_imports=True)
  • 1

此格式中各个参数的含义为:
obj:要转换的 Python 对象;
protocol:pickle 的转码协议,取值为 0、1、2、3、4,其中 0、1、2 对应 Python 早期的版本,3 和 4 则对应 Python 3.x 版本及之后的版本。未指定情况下,默认为 3。
其它参数:为了兼容 Python 2.x 版本而保留的参数,Python 3.x 中可以忽略。

【例 1】

import pickle
tup1 = ('I love Python', {1,2,3}, None)
#使用 dumps() 函数将 tup1 转成 p1
p1 = pickle.dumps(tup1)
print(p1)
输出结果为:
b'\x80\x03X\r\x00\x00\x00I love Pythonq\x00cbuiltins\nset\nq\x01]q\x02(K\x01K\x02K\x03e\x85q\x03Rq\x04N\x87q\x05.'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. pickle.loads()函数
    此函数用于将二进制对象转换成 Python 对象,其基本格式如下:
loads(data, *, fix_imports=True, encoding='ASCII', errors='strict')
  • 1

其中,data 参数表示要转换的二进制对象,其它参数只是为了兼容 Python 2.x 版本而保留的,可以忽略。

【例 2】在例 1 的基础上,将 p1 对象反序列化为 Python 对象。

import pickle
tup1 = ('I love Python', {1,2,3}, None)
p1 = pickle.dumps(tup1)
#使用 loads() 函数将 p1 转成 Python 对象
t2 = pickle.loads(p1)
print(t2)
运行结果为:
('I love Python', {1, 2, 3}, None)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注意,在使用 loads() 函数将二进制对象反序列化成 Python 对象时,会自动识别转码协议,所以不需要将转码协议当作参数传入。并且,当待转换的二进制对象的字节数超过 pickle 的 Python 对象时,多余的字节将被忽略。

  1. pickle.dump()函数
    此函数用于将 Python 对象转换成二进制文件,其基本语法格式为:
dump (obj, file,protocol=None, *, fix mports=True)
  • 1

其中各个参数的具体含义如下:
obj:要转换的 Python 对象。
file:转换到指定的二进制文件中,要求该文件必须是以"wb"的打开方式进行操作。
protocol:和 dumps() 函数中 protocol 参数的含义完全相同,因此这里不再重复描述。
其他参数:为了兼容以前 Python 2.x版本而保留的参数,可以忽略。

【例 3】将 tup1 元组转换成二进制对象文件。

import pickle
tup1 = ('I love Python', {1,2,3}, None)
#使用 dumps() 函数将 tup1 转成 p1
with open ("a.txt", 'wb') as f: #打开文件
    pickle.dump(tup1, f) #用 dump 函数将 Python 对象转成二进制对象文件
  • 1
  • 2
  • 3
  • 4
  • 5
'
运行

运行完此程序后,会在该程序文件同级目录中,生成 a.txt 文件,但由于其内容为二进制数据,因此直接打开会看到乱码。

  1. pickle.load()函数
    此函数和 dump() 函数相对应,用于将二进制对象文件转换成 Python 对象。该函数的基本语法格式为:
load(file, *, fix_imports=True, encoding='ASCII', errors='strict')
  • 1

其中,file 参数表示要转换的二进制对象文件(必须以 “rb” 的打开方式操作文件),其它参数只是为了兼容 Python 2.x 版本而保留的参数,可以忽略。

【例 4】将例 3 转换的 a.txt 二进制文件对象转换为 Python 对象。

import pickle
tup1 = ('I love Python', {1,2,3}, None)
#使用 dumps() 函数将 tup1 转成 p1
with open ("a.txt", 'wb') as f: #打开文件
    pickle.dump(tup1, f) #用 dump 函数将 Python 对象转成二进制对象文件
with open ("a.txt", 'rb') as f: #打开文件
    t3 = pickle.load(f) #将二进制文件对象转换成 Python 对象
    print(t3)
运行结果为:
('I love Python', {1, 2, 3}, None)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

总结
看似强大的 pickle 模块,其实也有它的短板,即 pickle 不支持并发地访问持久性对象,在复杂的系统环境下,尤其是读取海量数据时,使用 pickle 会使整个系统的I/O读取性能成为瓶颈。这种情况下,可以使用 ZODB。

ZODB 是一个健壮的、多用户的和面向对象的数据库系统,专门用于存储 Python 语言中的对象数据,它能够存储和管理任意复杂的 Python 对象,并支持事务操作和并发控制。并且,ZODB 也是在 Python 的序列化操作基础之上实现的,因此要想有效地使用 ZODB,必须先学好 pickle。
有关 ZODB 的详细介绍,读者可自行搜索相关文档,本节不再具体讲解。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/820589
推荐阅读
相关标签
  

闽ICP备14008679号