第五章 文件

程序如果想把数据永久保存在磁盘上,需要通过文件来保存

之前我们学的技术,编写程序时产生的数据都在内存中,当程序结束时,所有的数据都被清空,达不到永久保存的效果

狭义的文件就是指保存在硬盘上的数据

广义的文件是指一切IO,就是有输入和输出的接口,比如键盘,网络,显示器,都可以叫文件 

5.1 打开文件

在python中打开一个文件非常简单,使用内置的函数 open, 不过打开文件前,要先了解一下文件读取的模式

模式 含义
r 读取模式(默认)
w 写入模式
a 追加模式
b 二进制模式(与其它模式一起用)
t 文本模式(默认)(与其它模式一起用)
+ 读写模式(与其它模式一起用)

常见的文件打开模式

模式 含义
r

以文本方式打开文件,默认使用UTF-8解码

文件必须存在

文件打开后只可以读不可以写

w

以文本方式打开文件,默认使用UTF-8编码

如果文件存在会清空文件内容

文件打开后只能写不能读

文件打开后默认在文件开头处写

a

以追加方式打开文件,默认使用UTF-8编解码

文件必须存在

文件打开后只能写,而且写的时候总是在文件最后写

r+

以读写方式打开文件,文件必须存在,不会清空文件内容

文件打开后可写可读

文件打开后默认在文件开头处写

w+

以读写方式打开文件,如果文件存在会清空文件内容

文件打开后可写可读, 默认在文件开头处写

rb

以二进制方式打文件

文件打开后只能读

wb

以二进制方式打开文件

文件打开后只能写

open函数需要三个参数,操作文件的名字,文件的模式,编码

成功打开文件后返回一个文件对象,我们可以用read和write方法来操作这个文件对象

同样还有一个close()方法来关闭文件 

# 打开文件

f = open('testfile.txt', 'w+', encoding='gbk')
f.write("你好")
f.close()
# 读文件
f = open('testfile.txt', 'r+', encoding='gbk')
print(f.read())
f.close()

如果我们要处理文件内容不是文本类型,比如音频,视频文件,软件,压缩包等等,打开文件指定的模式,就必须要用二进制方式打开

否则按默认的文本模式打开二进制文件,会出错,因为这种纯二进制的文件,根本找不到任何编码可以解析出字符串形式

# 打开二进制文件,实现复制mp3文件
fin = open('1.mp3', 'rb')
fout = open('2.mp3', 'wb')
fout.write(fin.read())
fout.close()

 

5.2 操作文件

open方法返回的文件对象,已经知道至少有三个方法,read,write,close

我们可以写一段代码,列出文件对象所有可以调用的方法

# 文件方法
f = open('testfile.txt', 'w+', encoding='gbk')
l = [item for item in dir(f) if not item.startswith('_') and callable(getattr(f, item))]
print(l)
['close', 'detach', 'fileno', 'flush', 'isatty', 'read', 'readable', 'readline', 'readlines', 'reconfigure', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'writelines']
# 读方法
f = open('诛仙.txt', 'r')
print('read,按字符读取:%r'% f.read(3))
print('read,按字符读取:%r'% f.read(10))
print('readline,读取一行到列表中:%r' % f.readline())
print('readlines, 读取所有行到一个列表中:%r' %f.readlines()[:5])
f.close()
read,按字符读取:'时间:'
read,按字符读取:'不明,应该在很早、很'
readline,读取一行到列表中:'早以前。\n'
readlines, 读取所有行到一个列表中:['地点:神州浩土。\n', '天地不仁,以万物为刍狗!\n', '这世间本是没有什么神仙的,但自太古以来,人类眼见周遭世界,诸般奇异之事,电闪雷鸣,又有天灾人祸,伤亡无数,哀鸿遍野,决非人力所能为,所能抵挡。遂以为九天之上,有诸般神灵,九幽之下,亦是阴魂归处,阎罗殿堂。\n', '于是神仙之说,流传于世。无数人类子民,诚心叩拜,向着自己臆想创造出的各种神明顶礼膜拜,祈福诉苦,香火鼎盛。\n', '自古以来,凡人无不有一死。但世人皆恶死爱生,更有地府阎罗之说,平添了几分苦惧,在此之下,遂有长生不死之说。\n']
# 写方法

f = open('testfile.txt', 'w+')
f.write('春花秋月何时了,')
f.write('往事知多少?\n')
f.writelines(['小楼昨夜又东风,', '故国不堪回首月明中。'])

f.close()

f = open('testfile.txt', 'r')
print(f.read())
春花秋月何时了,往事知多少?
小楼昨夜又东风,故国不堪回首月明中。
#迭代
fin = open('诛仙.txt', 'r')
fout = open('诛仙2', 'w+')
for line in fin:
    fout.write(line)
    fout.write('\n')
fout.close()
fin.close()

5.2.1 文件缓存

由于直接操作文件读写,相当于对计算机的硬盘进行操作,直接读写硬盘相比于读写内存来说,要慢上几万倍的时间 

效率太低,所以操作系统给我们封装了一层,还刻操作系统的功能吗,操作系统直接控制底层的硬件,封装好之后提供给应用程序

硬盘就是计算机系统中的硬件,操作系统来对他们进行实际的读写,那我们在程序中写的读写程序,实际是在此之上进行的一种封装

操作系统在内存中给我们每个读写的文件分配一块空间,当我们读写文件的时候,实际上是在读写这一块内存空间,这样表面上看来我们是在读写文件

实际上是读写内存,这个效率一下就提高了,所以我们感觉读写文件好像挺快的

这块内存空间就叫文件的缓存。操作系统在适当的时候,会把缓存中的内容写到硬盘上去。

为什么我们操作文件完成后,必须要调用close()方法呢,在我们每次关闭文件的时候,操作系统就会把缓存中的内容,全部清空,并写到硬盘上去。

我们也可以调用 flush方法来完成强制刷新缓存,这个一般在我们写日志的时候,希望每句日志都能及时写在文件中,所以在写每一句后调用 flush方法强制把缓存中的内容写到硬盘中保存下来

 

 

5.2.2 二进制文件 

文本文件使用read方法,读取的单位是字符,二进制的文件,读取的单位就是字节

比如有一个压缩文件太大了,我们想把他拆成几份,就可以使用二进制的方式打开,然后按字节读出,再写到若干个文件中去

# 二进制文件 

SPLIT_MAX = 1000 * 1000 * 10
fin = open('python3.7.0.rar', 'rb')
data = fin.read(SPLIT_MAX)
i = 1
while data:
    fout = open('python_%s' % i , 'wb')
    fout.write(data)
    fout.close()
    i = i + 1
    data = fin.read(SPLIT_MAX)

 

文本文件也可以用二进制的模式打开,只不过打开的就是纯字节码

以二进制模式打开的文件,不能指定文件的解码方式,因为不涉及文本,所以就不涉及编码

如果把文本文件以二进制字节的形式读出来后,想要查看文本内容就需要我们自己用字符串的decode方法来解码

 

# 二进制打开文本文件
fin = open('诛仙.txt', 'rb')

data = fin.read(100 * 3)
print(data.decode('utf-8'))
时间:不明,应该在很早、很早以前。
地点:神州浩土。
天地不仁,以万物为刍狗!
这世间本是没有什么神仙的,但自太古以来,人类眼见周遭世界,诸般奇异之事,电闪雷鸣,又有天灾人祸,伤亡无数,哀鸿遍野,决非人力

 

其实我们在访问文件的时候,需要有一个光标来指示当前操作文件的位置 ,比如读数据的话,就是从光标所在的位置开始读

写数据就是从光标所在的位置开始写

打开一个文件时,光标也类似偏移量从0开始,打开一个文件光标默认就在文件开始,光标的位置就是0

查看光标位置,我们可以通过 tell方法来获取文件光标的位置,光标随着我们读和写都会向后移动 

#光标

fin = open('诛仙.txt', 'rb')

print(fin.tell())
fin.read(10)
print(fin.tell())
fin.close()

fin = open("testfile.txt", 'w+')
print(fin.tell())
fin.write('你好')
print(fin.tell())
0
10
0
6

我们也可以自己移动光标的位置, 使用seek方法可以定位,定位分为相对定位 和绝对定位 

通过seek的第二个参数来控制,0代表从文件头定位,1代表从当前位置定位 , 2代表从文件末尾

f = open("testfile.txt", 'w+')
f.write('123456789\nABCDEF\n一二三四五六七\n')
f.close()
f = open('testfile.txt', 'rb')
print(f.tell())
print(f.read(3))
print(f.tell())
f.seek(10, 0)
print(f.read(10).decode('utf-8'))

print(f.tell())
f.seek(-5, 1)
print(f.tell())
print(f.read(5).decode('utf-8'))

print(f.tell())
f.seek(-7, 2)
print(f.tell())
print(f.read().decode('utf-8'))

seek定位的话必须用二进制方式打开

如果seek第二参数为0,从文件开头定位的话,可以用文本方式打开

f = open("testfile.txt", 'w+')
f.write('123456789\nABCDEF\n一二三四五六七\n')
f.close()
f = open('testfile.txt', 'rb')
print(f.tell())
print(f.read(3))
print(f.tell())
f.seek(10, 0)
print(f.read(10).decode('utf-8'))

print(f.tell())
f.seek(-5, 1)
print(f.tell())
print(f.read(5).decode('utf-8'))

print(f.tell())
f.seek(-7, 2)
print(f.tell())
print(f.read().decode('utf-8'))
0
b'123'
3
ABCDEF
一
20
15
F
一
20
32
六七

 

5.3 标准文件 

我们已经知道,操作系统来控制我们每一个计算机的硬件

那对于操作系统上的应用程序,操作系统提供了三个文件来代替我们计算机系统的输入和输出 

这三个文件就叫标准 标准输入,标准输出,和标准错误

操作系统为每一个应用程序都提供了这三个文件 ,默认这三个文件 标准输入关联的是键盘

标准输出和标准错误关联的是显示器

在python中访问这三个访问,需要导入 sys模块

import sys
sys.stdout.write('hello')
sys.stdin.read(1)
sys.stderr.write('error')

 

 

 标准输入,标准输出,标准错误,默认是指我们的两个硬件,键盘和显示器

我们程序里input就是从键盘里读,print就是打印在显示器上

那我们同样可以自己打开一个文件对象,去替换stdin, stdout, stderr

这样之后,我们input就可以从文件里读,print就可以从文件里写

这种 替换标准对象的行为就叫重定向

# 重定向
import sys
fin = open('testfile.txt', 'r')
fout = open('output.txt', 'w')
sys.stdin = fin
sys.stdout = fout
sys.stderr = fout

s = input('>>>')
print(s)

 

我们一定要区别文件,并不只是硬盘上保存的文件

一切IO都是文件,再简单点说,就是有read或write方法的对象就是文件对象

我们的标准IO可以重定向到任何一个文件对象上

比如我们可以用类,自己定义一个文件对象

import sys
# 自定义文件对象
class MyFile:
    def __init__(self):
        self.buffer = ''

    def write(self, data):
        self.buffer += str(data)

    def flush(self):
        pass

    def readline(self):
        return '。。。'


sin = MyFile()
sout = MyFile()


sys.stdin = sin
sys.stdout = sout
print('hello')

s = input('>>>')
print(s)

如果用debug模式运行程序,我们会发现重定向后,我们输入,输出都会对我们自定义的文件对象操作

我们定义的readline方法会被input调用,print时会调用我们的write方法,并且会补一个\n

一般重定向输出用的比较多,也就是说我们只实现一个write和flush方法就可以定义出一个可以重定向的文件对象了。

 

posted @ 2020-07-20 17:34  人不知所  阅读(185)  评论(0)    收藏  举报