赞
踩
使用r+、w、w+、a、a+模式打开文件,可以写入,其中,r+、w、w+模式文件指针位于文件开头,a、a+指针位于结尾。文件指针的含义类似于我们使用word等文本编辑器时,光标的位置,我们可以在此位置读取或写入数据。
实现对文件指针的移动,文件对象提供了 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 个字符处。
注意:
使用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
前面章节中学习了如何使用 read()、readline() 和 readlines() 这 3 个函数读取文件,如果我们想把一些数据保存到文件中,又该如何实现呢?
Python 中的文件对象提供了 write() 函数和writelines()函数,可以向文件中写入指定内容。该函数的语法格式如下:
file.write(string)
file.writelines(迭代对象)
其中,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()
注意:
在写入文件完成后,一定要调用 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()
现在里面的内容是这样的:
采用不同的文件打开模式,会直接影响 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
例如,还是以 《春望.txt 》文件为例,通过使用 writelines() 函数,可以轻松实现将文件中的数据复制到其它文件中,实现代码如下:
f = open('春望.txt', 'r')
n = open('春望2.txt','w+')
n.writelines(f.readlines())
n.close()
f.close()
执行此代码,在 春望.txt 文件同级目录下会生成一个 春望2.txt 文件,且该文件中包含的数据和 春望.txt 完全一样。
需要注意的是,使用 writelines() 函数向文件中写入多行数据时,不会自动给各行添加换行符。上面例子中,之所以 b.txt 文件中会逐行显示数据,是因为 readlines() 函数在读取各行数据时,读入了行尾的换行符。
任何一门编程语言中,文件的输入输出、数据库的连接断开等,都是很常见的资源管理操作。但资源都是有限的,在写程序时,必须保证这些资源在使用过后得到释放,不然就容易造成资源泄露,轻者使得系统处理缓慢,严重时会使系统崩溃。
例如,前面在介绍文件操作时,一直强调打开的文件最后一定要关闭,否则会程序的运行造成意想不到的隐患。但是,即便使用 close() 做好了关闭文件的操作,如果在打开文件或文件操作过程中抛出了异常,还是无法及时关闭文件。
为了更好地避免此类问题,不同的编程语言都引入了不同的机制。在 Python 中,对应的解决方式是使用 with as 语句操作上下文管理器(context manager),它能够帮助我们自动分配并且释放资源。
简单的理解,同时包含 enter() 和 exit() 方法的对象就是上下文管理器。常见构建上下文管理器的方式有 2 种,分别是基于类实现和基于生成器实现。
例如,使用 with as 操作已经打开的文件对象(本身就是上下文管理器),无论期间是否抛出异常,都能保证 with as 语句执行完毕后自动关闭已经打开的文件。
首先学习如何使用 with as 语句。with as 语句的基本语法格式为:
with 表达式 [as target]:
代码块
此格式中,用 [] 括起来的部分可以使用,也可以省略。其中,target 参数用于指定一个变量,该语句会将 expression 指定的结果保存到该变量中。with as 语句中的代码块如果不想执行任何语句,可以直接使用 pass 语句代替。
>>> import fileinput >>> # 使用with语句打开文件,f该语句负责关闭文件 >>> with fileinput.input(files=('春望.txt','悯农.txt'))as f: ... for line in f: ... print(line) ... 春望 国破山河在,城春草木深。国破山河在,城春草木深。 烽火连三月,家书抵万金。 悯农 唐.李绅 春种一粒粟,秋收万颗子。 四海无闲田,农夫犹饿死。 锄禾日当午,汗滴禾下土。 谁知盘中餐,粒粒皆辛苦。
with as 语句实现的底层原理到底是怎样的呢?其实很简单,使用with语句管理的资源必须是一个实现上下文管理协议(context manage protocol)的类,这个类的对象可被称为上下文管理器。要实现上下文管理器,必须包含以下两个方法:
因此,只要一个类实现了__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代码块]~~~~~~~~~~异常之后的代码')
我们使用了两次with语句,第一次无异常,第二次抛出异常,通过输出结果对比可以看出with管理差异。
构造器初始化资源:孙悟空--------------1
[__enter__ 孙悟空]: -------------------2
as_object -----------------3
[with代码块]没有异常
[__exit__ 孙悟空]: ----------------4
没有异常关闭资源
-------------------------------------
构造器初始化资源:白骨精--------------1
[__enter__ 白骨精]: -------------------2
[with代码块]异常之前的代码 -----------------3
[__exit__ 孙悟空]: ----------------4
遇到异常关闭资源
Traceback(most recent call last):
....
可以看到程序执行顺序:
与目录相关的函数如下:
与权限相关的函数如下:
import os
ret = os.access(path,mode)
- os.F_OK:判断是否存在;
- os.R_OK:判断是否可读;
- os.W_OK:判断是否可写;
- os.X_OK:判断是否可执行;
示例:
import os,sys
#判断当前目录权限
ret = os.access('.',os.F_OK|os.R_OK|os.W_OK|os.X_OK)
prinnt(ret) #Ture
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将该文件设为可写的
与文件访问相关函数如下:
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:不追踪软连接
>>> 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)
需要注意的是,这里的文件描述符已经没有tell()方法了。
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)
上面程序以两种方式来创建临时文件:
第一种方式是手动创建临时文件,读写临时文件后需要主动关闭它,当程序关闭该临时文件时,该文件会被自动删除。
第二种方式则是使用 with 语句创建临时文件,这样 with 语句会自动关闭临时文件。
上面程序最后还创建了临时目录。由于程序使用 with 语句来管理临时目录,因此程序也会自动删除该临时目录。
运行上面程序,可以看到如下输出结果:
C:\Users\admin\AppData\Local\Temp\tmphvehw9z1
两情若是久长时,又岂在朝朝暮暮。
b'I Love Python!'
创建临时目录C:\Users\admin\AppData\Local\Temp\tmp3sjbnwob
上面第一行输出结果就是程序生成的临时文件的文件名,最后一行输出结果就是程序生成的临时目录的目录名。需要注意的是,不要去找临时文件或临时文件夹,因为程序退出时该临时文件和临时文件夹都会被删除。
Python 中有个序列化过程叫作 pickle,它能够实现任意对象与文本之间的相互转化,也可以实现任意对象与二进制之间的相互转化。也就是说,pickle 可以实现 Python 对象的存储及恢复。
值得一提的是,pickle 是 python 语言的一个标准模块,安装 python 的同时就已经安装了 pickle 库,因此它不需要再单独安装,使用 import 将其导入到程序中,就可以直接使用。
pickle 模块提供了以下 4 个函数供我们使用:
以上这 4 个函数可以分成两类,其中 dumps 和 loads 实现基于内存的 Python 对象与二进制互转;dump 和 load 实现基于文件的 Python 对象与二进制互转。
dumps(obj, protocol=None, *, fix_imports=True)
此格式中各个参数的含义为:
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.'
loads(data, *, fix_imports=True, encoding='ASCII', errors='strict')
其中,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)
注意,在使用 loads() 函数将二进制对象反序列化成 Python 对象时,会自动识别转码协议,所以不需要将转码协议当作参数传入。并且,当待转换的二进制对象的字节数超过 pickle 的 Python 对象时,多余的字节将被忽略。
dump (obj, file,protocol=None, *, fix mports=True)
其中各个参数的具体含义如下:
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 对象转成二进制对象文件
运行完此程序后,会在该程序文件同级目录中,生成 a.txt 文件,但由于其内容为二进制数据,因此直接打开会看到乱码。
load(file, *, fix_imports=True, encoding='ASCII', errors='strict')
其中,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)
总结
看似强大的 pickle 模块,其实也有它的短板,即 pickle 不支持并发地访问持久性对象,在复杂的系统环境下,尤其是读取海量数据时,使用 pickle 会使整个系统的I/O读取性能成为瓶颈。这种情况下,可以使用 ZODB。
ZODB 是一个健壮的、多用户的和面向对象的数据库系统,专门用于存储 Python 语言中的对象数据,它能够存储和管理任意复杂的 Python 对象,并支持事务操作和并发控制。并且,ZODB 也是在 Python 的序列化操作基础之上实现的,因此要想有效地使用 ZODB,必须先学好 pickle。
有关 ZODB 的详细介绍,读者可自行搜索相关文档,本节不再具体讲解。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。