第十五篇 Python之文件处理

一 文件操作

  1.  介绍

计算机系统分为:计算机硬件,操作系统,应用程序三部分。

我们用python或其他语言编写的应用程序若想要把数据永久保存下来,必须要保存于硬盘中,这就涉及到应用程序要操作硬件,众所周知,应用程序是无法直接操作硬件的,这就用到了操作系统。操作系统把复杂的硬件操作封装成简单的接口给用户/应用程序使用,其中文件就是操作系统提供给应用程序来操作硬盘虚拟概念,用户或应用程序通过操作文件,可以将自己的数据永久保存下来。

有了文件的概念,我们无需再去考虑操作硬盘的细节,只需要关注操作文件的流程

#1. 打开文件,得到文件句柄并赋值给一个变量
#2. 通过句柄对文件进行操作
#3. 关闭文件

什么是句柄?
1.代码的运行是在内存中运行的,在下一步要读文件内容,而文件内容在硬盘上,所以需要将硬盘上的文件加载到内存中,open()函数则提供了这个功能,相当于向操作系统要了一个鱼网,这个鱼网
就是句柄。open实际上是在调操作系统,由操作系统最终给你返回一个鱼网,即句柄
将句柄赋值给一个变量f,有了这个f句柄,就可以操作硬盘的文件了。
2.有了鱼网(f),下一步想要什么鱼,就用鱼网捞就行了,或者想往操作系统里放什么鱼,都可以通过鱼网进行。对应的代码就是read,write等方法,如:f.read()
3.关闭文件.实际上关闭的不是文件,而是将鱼网回收。如果不关闭,那操作系统就一直给你发放鱼网,就会占用操作系统的资源一直不释放。

重点强调的

#强调第一点:
打开一个文件包含两部分资源:操作系统级打开的文件+应用程序的变量。在操作完毕一个文件时,必须把与该文件的这两部分资源一个不落地回收,回收方法为:
1、f.close() #回收操作系统级打开的文件
2、del f #回收应用程序级的变量

其中del f一定要发生在f.close()之后,否则就会导致操作系统打开的文件还没有关闭,白白占用资源,
而python自动的垃圾回收机制决定了我们无需考虑del f,这就要求我们,在操作完毕文件后,一定要记住f.close()

为了避免忘记f.close(),推荐傻瓜式操作方式:使用with关键字来帮我们管理上下文
with open('a.txt','w') as f:
    pass
 
with open('a.txt','r') as read_f,open('b.txt','w') as write_f:
    data=read_f.read()
    write_f.write(data)

强调第一点:资源回收


#强调第二点:
f=open(...)是由操作系统打开文件,那么如果我们没有为open指定编码,那么打开文件的默认编码很明显是操作系统说了算了,操作系统会用自己的默认编码去打开文件,在windows下是gbk,在linux下是utf-8。
这就用到了上节课讲的字符编码的知识:若要保证不乱码,文件以什么方式存的,就要以什么方式打开。

f=open('a.txt','r',encoding='utf-8')

强调第二点:字符编码

 2.  Python 中的进行文件处理的流程示例

#一. 打开文件,得到文件句柄并赋值给一个变量
f=open('a.txt', 'r', encoding='utf-8') 

打开文件的过程中需要注意的几个问题:
# 1.文件路径:示例中的文件没有加路径,是因为该文件与python文件是在同一个目录的;如果不在同一个目录就需要指定文件路径
# 2.默认打开模式就为r
# 3.open不是找Python的编码,open函数默认的是检索你所用的操作系统的编码。所以打开文件为避免异常,需要指定编码格式,文件用什么编码格式,打开文件就需要指定对应的编码格式。
# 4.如果Pycharm写的文件默认保存为 utf-8;
# 5.mac默认的文件编码是utf-8


#二. 通过句柄对文件进行操作
data=f.read()

#三. 关闭文件
f.close()
View Code

  3.  f = open('a.txt','r')的过程分析

#1、由应用程序向操作系统发起系统调用open(...)

#2、操作系统打开该文件,并返回一个文件句柄给应用程序

#3、应用程序将文件句柄赋值给变量f

 二 打开文件的模式

文件句柄 = open('文件路径', '模式')

 模式可以是下面的一种或者他们之间的组合:

Character Meaning
‘r' open for reading (default)
‘w' open for writing, truncating the file first
‘a' open for writing, appending to the end of the file if it exists
‘b' binary mode
‘t' text mode (default)
‘+' open a disk file for updating (reading and writing)
‘U' universal newline mode (for backwards compatibility; should not be used in new code)
#1. 打开文件的模式有(默认为文本模式):
r, 只读模式【默认模式,文件必须存在,不存在则抛出异常】
w, 只写模式【不可读;不存在则创建;存在则清空内容】
a, 只追加写模式【不可读;不存在则创建;存在则只追加内容】

#2. 对于非文本文件,我们只能使用b模式,"b"表示以字节的方式操作(而所有文件也都是以字节的形式存储的,使用这种模式无需考虑文本文件的字符编码、图片文件的jgp格式、视频文件的avi格式)
rb 
wb
ab
注:以b方式打开时,读取到的内容是字节类型,写入时也需要提供字节类型,不能指定编码

#3. 了解部分
"+" 表示可以同时读写某个文件
r+, 读写【可读,可写】
w+,写读【可读,可写】
a+, 写读【可读,可写】


x, 只写模式【不可读;不存在则创建,存在则报错】
x+ ,写读【可读,可写】
xb
# 回车与换行的来龙去脉
http://www.cnblogs.com/linhaifeng/articles/8477592.html

# U模式
'U' mode is deprecated and will raise an exception in future versions
of Python.  It has no effect in Python 3.  Use newline to control
universal newlines mode.

# 总结:
在python3中使用默认的newline=None即可,换行符无论何种平台统一用
即可

了解U模式与换行符
三 操作文件的方法
三种最常用,最纯净的文件操作模式:r、w、a 模式

1. r 模式 读操作
f = open('test.txt','r',encoding = 'utf-8')

# r 模式,读的方法
# data = f.read()  # read代表读取文件全部
print(f.readable())   # True 表示该文件可读

print('第一行',f.readline())   # 一次读一行。即使文件里有多行,运行一次也只执行一行。
print('第二行',f.readline()) 
print('第三行',f.readline()) 
print('第四行',f.readline()) 
# 结果
第一行 你好啊,

第二行 adb

第三行 djfafjad 

第四行 世界你好

# readline()读取文件的结果,每行之间都空着一行,这是因为每行结尾的时候都有个回车空格导致,解决方法如下:
print('1行',f.readline(),end = "")
print('2行',f.readline(),end = "")
print('3行',f.readline(),end = "")
print('4行',f.readline(),end = "")

# 结果 , 就把上面的空行给去掉了。
1行 你好啊,
2行 adb
3行 djfafjad 
4行 世界你好

print(f.readlines())  # 读取文件内容,并放到一个列表里

# 结果
['你好啊,
', 'adb
', 'djfafjad 
', '世界你好
']
View Code

2. w 模式 写操作

f = open('test.txt','w',encoding = 'utf-8')

f.read()   
# 报错  io.UnsupportedOperation: not readable
# 原因:句柄指定的是写方法,不可读的。
View Code
f = open('test.txt','w',encoding = 'utf-8')

# w 写模式,都是新建一个空文档,直接覆盖原来的文件。

# test.txt 文件存在,直接运行上面的文件,会清空test.txt文件

f1 = open('test1.txt','w',encoding = 'utf-8')

# test1.txt文件不存在,会直接新建一个test1.txt空文件

f1.write('1111
')
f1.write('1232
')
f1.write('1232
')
f1.write('1111
fjdfl
jadlj')
f1.write('接着上面的行写,不会换行')

# 如过写文件内容的时候没有加
换行符,就会直接紧挨着写
# 写到test1.txt文件里的结果
1111
1232
1232
1111
fjdfl
jadlj接着上面的行写,不会换行 

print(f1.writable())    # True   代表可写入


# writelines 是传入一个列表,列表里写上要写入文件的内容
f1.writelines(['555
','666
'])

# 写到test1.txt文件里的结果
1111
1232
1232
1111
fjdfl
jadlj接着上面的行写,不会换行
555
666

# 注意:
读取、写入文件里的内容全都是字符串,如果输入非字符串类型就会报错

f1.write(3)
# TypeError: write() argument must be str, not int
View Code

 3. a 模式 追加操作

a 模式本身就是写模式,只不过是以追加的方式写到文件的最后。

f=open('test1','a',encoding='utf-8')
f.write('写到文件最后')

#结果
11111111
222222222
333
4444
555
555
6666
555
6666
写到文件最后
View Code

其他文件操作模式:

4. r+ 模式:既能读,又能写

示例1:
 # 1. 先打开文件
f = open('rj','r+',encoding= 'utf-8')  
 # 2. 再读取文件 
data = f.read()                                
print(data) 
# 结果
你是谁

# 3. 最后写入文件(此时经过读取,光标已经定位在最后了,所以新写入的内容都在原文件内容的最后面)

f.write('123ssn')                              
#结果
你是谁123ssn    

# 新写入的123ssn为什么直接写在了原文件内容的后面,而没有换行呢?
因为原文件内容写完  你是谁之后没有按回车键进行换行,所以直接接着写了。
如果在你是谁之后操作了回车换行,再写入就新起了一行

# 结果
你是谁
123ssn 


示例2:
# 打开文件,没有读,直接写
f = open('rj','r+',encoding= 'utf-8')
f.write('sb')

#结果
123ssn谁

# 123ssn为什么会写在最前面?“你是”两个字哪里去了?
文件根本就没有修改这么一说,全都是覆盖。
所以一打开文件,光标就停留在最开始的位置写入是从光标所在的位置开始写的,所以123ssn会写在最前面,后面的内容也被覆盖掉了。

# 那么我们平时常见的文件修改是怎么完成的呢?
首先,要改是通过软件(word/txt,cat等)打开文件,将文件内容全部都加载到了内存当中
其次,在内存当中是可以修改文件的
最后,当文件修改完成后,还是需要通过软件保存文件。而软件会将内存中的文件全部覆盖回去到硬盘上。
这就是我们看到的对文件的修改。

示例3:模拟文件的修改过程:
原文件的内容:
你是谁,
who am i ?


1. 先打开了源文件
src_file = open('rj','r',encoding = 'utf-8')   读的方式打开
2. 读取文件内容并复制给一个变量,存在内存里
data = src_file.readlines()   # 读取的文件内容是个列表了
3. 关闭原文件
src_file.close()

4. 再打开了一个新的文件,因为刚才的文件内容已经读取到且放在了内存里,所以此时再写,就可以指定其他的编码方式了
dst_f = open('rj' , 'w', encoding = 'gbk')

5. 模拟修改文件的,所以只需写入第一行文件内容
dst_f.write(data[0])  

# 修改完之后,文件内容如下:
你是谁,
View Code

5. with 关键字

上面的方法打开文件后,都需要专门写个close()方法关闭文件,而使用with关键字则可以一步到,不用再单独写close()关闭文件了。

示例1:
with open('rj', 'w', encoding='utf-8') as f:
    f.write('with方式打开文件
')
    
# open('rj', 'w', encoding='utf-8') as f 这个语句意思等于 f = open('rj', 'w', encoding='utf-8')
# 只不过加上了with关键字,就不需要再专门写个close()方法来关闭文件了

示例2:with 同时打开两个文件

# Python里一行不要写太长的代码,可以通过  来换行连接
# 需求:从原文件里读到的内容,直接不做修改,写到新的文件里

with open('rj', 'r', encoding='utf-8') as f, 
        open('rj_new', 'w', encoding= 'utf-8') as dis_file:
    data = f.read()
    dis_file.write(data)
View Code

文件的高级操作:

Linux处理文件全部都是二进制的方式

6. 文件处理b模式

写在最前:b的方式不能指定编码,无论是rb,wb,ab都不能指定编码

为什么要用二进制?

二进制代表处理数据的方式,并不代表最后的内容。文件默认处理就是以t模式进行,即文本模式,所以 r = rt, w = wt, a =at, 只是省略了t。

1. 文件并不仅仅是文本,还有图片,视频,音频等,如果还以t的方式就处理不了,就需要以二进制的方式处理,即b方式处理,这是由文件的类型决定的。

2. 二进制这种方式可以跨平台,Linux,Windows,unit等各种操作系统,都是使用硬件(如硬盘)来处理数据,操作系统把文件交给硬盘,而硬盘存储的都是二进制的内容,所以操作系统给硬盘的文件都得是二进制的。所以以二进制方式处理文件,你的文件就可以跨平台。

3. b 模式对Linux没什么卵用,因为Linux默认就是二进制处理,b模式对Windows系统有用。

6-1   rb 模式

#rb模式

要读取的test11.py里文件的内容如下:
hello1
22222
33333
4444
你好啊林师傅

f=open('test11.py','rb',encoding='utf-8') 
# 结果:会报错 
ValueError: binary mode doesn't take an encoding argument

# 原因:文件存储在硬盘上是以二进制方式存储的,而读的时候就告诉了open函数,以 b(字节)的方式进行读取的,读取的就是原生的二进制,然后又指定编码,这是什么意思?是无意义的了。
#所以,b的方式不能指定编码


# 正确的读取方式
f = open('test11.py','rb')   #b的方式不能指定编码,读取就正确了
data = f.read()
print(data)

# 结果
b'hello1
22222
33333
4444
xe4xbdxa0xe5xa5xbdxe5x95x8axe6x9ex97xe5xb8x88xe5x82x85'

结果解析:
b:代表读出来的是原生的字节形式

:Windows里的回车键就是以
表示的,表示一个换行
xe4xbdxa0xe5xa5xbdxe5x95x8axe6x9ex97xe5xb8x88xe5x82x85:因为读取的就是二进制,所以中文在硬盘上就是这么存储的。

# 那还是以b的方式读取,就想看到转译后的人类可读的文字,怎么办?
将二进制转换成字符串的形式就可以了。

文件往硬盘里存的时候,在内存里是字符串的形式,通过编码之后,以字节的方式存在硬盘里的:
字符串(内存) -----> encoding ------> bytes(硬盘)

所以,想让人能看懂,先从硬盘读取,然后解码到内存:
bytes(硬盘) ----->decode ------> 字符串

f = open('test11.py','rb')   
data = f.read()
print(data.decode('utf-8'))  # 

# 结果 : 就变成人类可读的语言了
hello1
22222
33333
4444
你好啊林师傅
View Code

6-2   wb模式

# 指定要以b模式写文件
f=open('test22.py','wb') #b的方式不能指定编码

# 方式1:通过bytes函数将字符串进行编码,转换成字节形式写入文件
f.write(bytes('1111
',encoding='utf-8'))  # 把字符串转换为字节写入

# 方式2:字符串本身就具有encode方法,所以可以直接 .encode()进行编码然后写入
f.write('杨件'.encode('utf-8'))   

# 结果
1111
杨件
View Code

6-3  ab模式

f=open('test22.py','ab') #b的方式不能指定编码
f.write('杨老二'.encode('utf-8'))
# a追加,a不代表在最后一行追加写入,而是代表在文件最后的光标位置开始追加写入
# 举例:最后一行有换行,那光标就是定位在有文字内容的下一行的

# 结果
1111
杨件
杨老二
View Code

7. 文件的其他操作方法

 

f = open('www.txt','w',encoding= 'gbk')

# closed:查看文件是否关闭
print(f.closed)    # False:表示文件是打开的,  True:表示文件是关闭了

# 得到的是文件打开的编码方式,而不是原文件的存储编码; 如果文件打开模式为b,则没有该属性
f = open('www.txt','w',encoding= 'gbk')
print(f.encoding)        # 这个方法指的是open()函数,打开文件时,encoding = "gbk" 这个编码

# 如果真不知道文件编码,怎么办?
# latin-1: 拉丁编码方式,会最大程度的保证文件能正确读出来---欠着,貌似还有不对的地方。
f = open('www.txt','r', encoding = 'latin-1')
data = f.read()
print(data)
f = open('www.txt','w',encoding= 'gbk')
print(f.fileno())  # 文件描述符,没啥用
print(f.errors)    # 关于文件的报错,没啥大用
f.flush()  # 刷新文件,将写在内存里的文件刷新保存到硬盘上去(通过cmd终端演示能看到效果)
f.isatty()  # 看你用的是不是个终端设备
print(f.name)   # www.txt   看文件的内容
文件内容如下:
naidfna kfad
jfalkdj爱看的积
分安监局卡的房间大
了借款方


f = open('www123.txt','r+',encoding= 'utf-8')
# f.write("naidfna kfad 
jfalkdj
爱看的积分安监局卡的
房间大了借款方  ")

print(f.readlines())
# 结果
# ['naidfna kfad
', 'jfalkdj爱看的积
', '分安监局卡的房间大
', '了借款方']

这里为啥是
, 因为换行符被python3多余处理了一下,去掉了
(Windows)

# 让python保持原样,文件里本来的换行符是什么就显示什么,不做处理,需要在读取的时候 指定 newline = ''

# 读取文件中真正的换行符号
f = open('www123.txt','r+',encoding= 'utf-8' ,newline = '')

# 结果,就显示 
了。
['naidfna kfad
', 'jfalkdj爱看的积
', '分安监局卡的房间大
', '了借款方']

   8. 文件内光标移动(文件操作方法)

  一: read(3):

    1. 文件打开方式为文本模式时,代表读取3个字符

    2. 文件打开方式为b模式时,代表读取3个字节

  二: 其余的文件内光标移动都是以字节为单位如seek,tell,truncate

  注意:

    1. seek有三种移动方式0,1,2,其中1和2必须在b模式下进行,但无论哪种模式,都是以bytes为单位移动的

    2. truncate是截断文件,所以文件的打开方式必须可写,但是不能用w或w+等方式打开,因为那样直接清空文件了,所以truncate要在r+或a或a+等模式下测试效果

1. tell()方法

# 告诉当前文件光标所在的位置,因为文件存储的都是二进制的形式,utf-8一个汉字占3个字节,一个换行也占3个字节(
)
f = open('www123.txt','r+',encoding= 'utf-8' ,newline = '')
f.readline()    # 读一行文件
print(f.tell())   # 14,  读了上面文件内容一行后,当前文件光标的位置在第14个字节处

2. seek()方法

#  seek():用来控制光标的移动, 0 ,1 ,2 等表示字节数
f.seek(0)     
f.seek(3)  # utf-8一个汉字占3个字节,所以对汉字至少是3的倍数进行seek,否则就会报错

# seek 默认是从文件开头移动的
with open('seek.txt', 'r', encoding= 'utf-8') as f:
    print(f.tell())   # 0   刚打开文件,读取的光标位置为0
    print(f.seek(10)) # 10  光标移动了10字节
    print(f.tell())   # 10
    print(f.seek(3))  # 3   上面已经seek 10个字节了,这里为啥不是13呢? 还是前面说的,seek默认是从文件开头移动光标的。    
    print(f.tell())   # 3

备注:
其实seek()模式是从0开始的,上面的
seek(10)  其实 等于    seek(10, 0) : 
0 表示每次seek都是从文件开头移动

seek移动是以字节的方式进行,而0是默认从开头移动,0这种方式模式就是以b字节模式进行移动,所以不用指定b模式。
而以 1 这种相对位置进行seek,必须指定 b模式,而二进制还不能指定打开文件的编码方式

# 基于相对位置的seek
seek(10,1):   
1 代表相对位置,相对于上一次光标移动的位置是10,那么下一次移动就从10这个相对位置开始再往后移动

with open('seek.txt', 'rb', encoding= 'utf-8') as f:
    print(f.seek(10, 1))

# 报错
io.UnsupportedOperation: can't do nonzero cur-relative seeks:以相对位置进行seek,必须指定b模式
ValueError: binary mode doesn't take an encoding argument:
以b模式处理文件,就不能进行其他编码
所以,seek的相对位置移动,正确的处理方法如下:

with open('seek.txt', 'rb') as f:
    print(f.tell())         # 0
    print(f.seek(10, 1))    # 10
    print(f.tell())         # 10
    print(f.seek(3,1))      # 13
    print(f.tell())         # 13

最有用的seek,从文件末尾倒叙往前seek,然后读取的时候,就是从光标所在位置开始,读取后面的所有内容

文件内容:
你好

hello

546

123


# 注意,为了能看出效果,上面的换行符号用
表示,实际是不可能这样写的。
windows下,换行符 = 
, 一个换行符就占2个字节

with open('seek.txt', 'rb') as f:
    print(f.tell())            # 0
    print(f.seek(-10, 2))      # 倒序seek:从文件末尾开始往前seek10个字符
    print(f.read())   # b'
546
123'

倒序seek有啥用嘞?
读日志,因为看日志永远都是看最新的日志,而最新的日子都在文件的最后面

# 读取的日志文件的最后一行

# 1. 常规读法
with open('日志文件', 'rb') as f:
    data = f.readlines()
    print(data[-1])

# 2. 用seek去读
f=open('日志文件','rb')

# 循环文件的推荐方式
for i in f:
    offs=-10    #  # 定义一个偏移量,这个需要自己大概估算一下,日志文件行有多少个字节
    while True:
        f.seek(offs,2)
        data=f.readlines()
        # 当data的长度大于1说明最后一行以及倒数第二行已经读出来了,然后就break
        if len(data) > 1:
            print('最后一行是%s' %(data[-1].decode('utf-8')))
            break
        offs*=2     # 不断加大光标偏移量

f.close()


3. truncate() 方法

# 截取:代表从文件内容的开头到第十个字节
# truncate 其实是写方法,表示从0到第10个字节,这些内容截取保留下来,其他的文件内容都删除掉
# 所以,文件的打开方式不能以w+、w 的方式打开,因为w+、w 一打开就把原文件清空了,那还截取什么呢?其他任何 r+,a+等等都可以,就w+不行。

f = open('www123.txt','r+',encoding= 'utf-8' ,newline = '')
f.truncate(10)

4. read()方法

f.read(2) # 代表读2个字符。一个汉字就是一个字符,一个英文字母也是一个字符,一个标点符号还是一个字符。

# 除了read()方法,其他所有的方法在处理文件的光标的时候,统一都是按照字节来处理      

 9 .文件的修改

文件的数据是存放于硬盘上的,因而只存在覆盖、不存在修改这么一说,我们平时看到的修改文件,都是模拟出来的效果,具体的说有两种实现方式:

方式一:将硬盘存放的该文件的内容全部加载到内存,在内存中是可以修改的,修改完毕后,再由内存覆盖到硬盘(word,vim,nodpad++等编辑器)

import os

with open('a.txt') as read_f,open('.a.txt.swap','w') as write_f:
    data=read_f.read() #全部读入内存,如果文件很大,会很卡
    data=data.replace('alex','SB') #在内存中完成修改

    write_f.write(data) #一次性写入新文件

os.remove('a.txt')
os.rename('.a.txt.swap','a.txt')

方式二:将硬盘存放的该文件的内容一行一行地读入内存,修改完毕就写入新文件,最后用新文件覆盖源文件

import os

with open('a.txt') as read_f,open('.a.txt.swap','w') as write_f:
    for line in read_f:
        line=line.replace('alex','SB')     # 把 'alex' 字符串替换为 'SB’
        write_f.write(line)

os.remove('a.txt')
os.rename('.a.txt.swap','a.txt')
原文地址:https://www.cnblogs.com/victorm/p/9177665.html