python-2/08-Python文件处理.md
2024-12-26 09:59:55 +08:00

27 KiB
Raw Blame History

08-Python文件处理

一、打开关闭文件

可以用 file 对象做大部分的文件操作。 file()在python3中已经被废除使用open()打开文件

open 函数 先用open()打开一个文件创建一个file

对象,再用相关方法才可以调用它进行读写。

语法

file object = open(file_name [, access_mode][, buffering]) 

参数说明 file_name file_name变量是一个包含了你要访问的文件名称的字符串值。

access_mode 决定打开文件的模式:只读'r',写入'w',追加'a'等,所有可取值见后面的完全列表,这个参数是非强制的,默认文件访问模式为只读(r)。

buffering: 如果buffering值被设为0python3已经不能使用0了就不会有寄存数据会立即写入文件。 如果buffering值取1默认值访问文件时会寄存行直到文件关闭才会把数据同步到文件。一般缓冲使用系统默认值即可. 如果将buffering的值设为大于1的整数表明这就是的寄存区的缓冲大小。缓冲内容超过这个大小之后就会同步硬盘 如果取负值,寄存区的缓冲大小则为系统默认。

例1. >>> f = open('c.txt','w',0) #没有缓冲 >>> f.write('hello world') #close之前打开另一终端观察文件发现已经写入进去说明关闭文件之前就已经写到硬盘 >>> f.close()

例2. >>> f = open('d.txt','w',1) #有缓冲 >>> f.write('hello world') #close之前打开另一终端观察文件发现没有写入进去说明关闭文件之前没有写到硬盘 >>> f.close()

不同模式打开文件的完全列表

模式 描述
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。以这种模式打开的文件必须是已经存在的(U模式也是)
rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。
r+ 打开一个文件用于读写。文件指针将会放在文件的开头。注意这个模式默认是带缓冲的
rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
w 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
w+ 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后即使你 seek 到了其它的地方。如果该文件不存在,创建新文件进行写入。
ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

通用换行符支持(UNS) 还有一个特殊的模式U,不同平台用来表示行结束的符号是不同的, 例如 \n, \r, 或者 \r\n.当你使用 'U' 标志打开文件的时候, 所有的行分割符(或行结束符, 无论它原来是什么)通过 Python 的输入方法(例如 read*() )返回时都会被替换为换行符 NEWLINE(\n).注意 UNS 只用于读取文本文件. 没有对应的处理文件输出的方法.

可以使用U,rU或Ua模式打开文件

关于 'b' 的说明 对于所有 POSIX 兼容的 Unix 系统(包括Linux)来说, 'b'是可有可无的, 因为它们把所有的文件当作二进制文件, 包括文本文件. 下面是从 Linux 手册的 fopen() 函数使用中摘录的一段, Python 语言中的 open() 函数就是从它衍生出的: 指示文件打开模式的字符串中也可以包含字符 "b" , 但它不能做为第一个字符出现. 这样做的目的是为了严格地满足 ANSI C3.159-1989 (即 ANSI C)中的规定。 事实上它没有任何效果, 所有POSIX 兼容系统, 包括 Linux , 都会忽略 "b" ,其它系统可能会区分文本文件和二进制文件, 如果你要处理一个二进制文件, 并希望你的程序可以移植到其它非 Unix 的环境中, 加上"b" 会是不错的主意

扩展阅读:

计算机在物理内存上面存放的都是二进制所以文本文件和二进制文件的主要区别是在逻辑上的而不是物理上的。而从文件的编码方式来看文件可以分为文本文件和二进制文件。文本文件是基于字符编码的文件常见的有ASCII、Unicode等二进制文件是基于值编码的文件可以看成是变长编码你可以根据自己的需要决定多少个比特代表一个值。

从文件编码的方式来看文件可分为ASCII码文件和二进制码文件两种。
(1)ASCII文件也称为文本文件这种文件在磁盘中存放时每个字符对应一个字节用于存放对应的ASCII码。例如数5678的存储形式为
    
    ASC码    00110101 00110110 00110111 00111000
          ↓     ↓      ↓      
    十进制码:  5      6     7     8 共占用4个字节。ASCII码文件可在屏幕上按字符显示 例如源程序文件就是ASCII文件用DOS命令TYPE可显示文件的内容。 由于是按字符显示,因此能读懂文件内容。

(2)二进制文件是按二进制的编码方式来存放文件的。 例如, 数5678的存储形式为 00010110 00101110只占二个字节。二进制文件虽然也可在屏幕上显示但其内容无法读懂。C系统在处理这些文件时并不区分类型都看成是字符流按字节进行处理。 输入输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。 因此也把这种文件称作“流式文件”。

存储的方式不同
    二进制文件就是把内存中的数据按其在内存中存储的形式原样输出到磁盘中存放,即存放的是数据的原形式。
    文本文件是把数据的终端形式的二进制数据输出到磁盘上存放,即存放的是数据的终端形式

在实际存储中最好是将数据分成字符数据和非字符数据两类:
    (1)如果存储的是字符数据,无论采用文本文件还是二进制文件都是没有任何区别的,所以讨论使用文本文件还是二进制文件是没有意义的。
    (2)如果存储的是非字符数据,又要看我们使用的情况来决定:
        a:如果是需要频繁的保存和访问数据,那么应该采取二进制文件进行存放,这样可以节省存储空间和转换时间。
        b:如果需要频繁的向终端显示数据或从终端读入数据,那么应该采用文本文件进行存放,这样可以节省转换时间。

文本文件的打开方式和二进制文件打开方式的区别
(1)文本模式中回车被当成一个字符'\n'在文件中如果读到0x1B,文本模式会认为这是文件结束符,会按照一定方式对数据做相应的转换。
(2)二进制模式中'\n'会被认为是两个字符0x0D0x0A;在读到0x1B时二进制模式不会对文件进行处理。

只读/写模式的r/w和rb/wb
(1)r:读取到的是文本数据(字符的编码),使用 open() 打开文件时,默认采用 GBK 编码。但当要打开的文件不是 GBK 编码格式时,可以在使用 open() 函数时手动指定打开文件的编码格式例如file = open("a.txt",encoding="utf-8")

(2)rb: 读取到的是二进制数据(字符在硬盘中存储的二进制),不需要指定编码.

file对象的属性 一个文件被打开后你就会拥有一个file对象你可以得到有关该文件的各种信息。

file对象属性列表

属性 描述
file.closed 如果文件已被关闭返回true否则返回false。
file.mode 返回被打开文件的访问模式。
file.name 返回文件的名称。

例:

#!/usr/bin/python3.8
# 打开一个文件
fo = open("foo.txt", "w")
print("文件名: ", fo.name)
print("是否已关闭 : ", fo.closed)
print("访问模式 : ", fo.mode)

输出结果 
文件名:  foo.txt
是否已关闭 :  False
访问模式 :  w

close()方法 File 对象的 close方法刷新缓冲区里任何还没写入的信息并关闭该文件这之后便不能再进行写入。

语法: fileObject.close() 例:

#!/usr/bin/python3.8
fo = open("foo.txt", "w")
print("文件名: ", fo.name)
fo.close()      # 关闭打开的文件

输出结果 
文件名:  foo.txt 

with

用with可以不用关闭文件

with open('/proc/meminfo') as fd: 和 fd=open('/proc/meminfo') 执行的结果一样,都是遍历文件; 后者当打开的文件达到几个G的时候很消耗内存,而前者没有这个问题

#!/usr/bin/env python3.8
with open('a.txt','w') as f:
    f.write('hello world')
    
with open('a.txt','r') as f:
    print(f.read())

二、读文件

使用方法read() readline() readlines()

read():返回字符串 从一个打开的文件中读取一个字符串。 fileObject.read([count]) 被传递的参数count是要从已打开文件中读取的字节计数。 该方法从文件的开头开始读入如果没有传入count参数(默认值为 -1)或者值为负, 文件将被读取到末尾.

#!/usr/bin/python3.8
fo = open("foo.txt", "r+")
str = fo.read(10);
print("读取的字符串是 : ", str)
fo.close() 

输出结果 
     读取的字符串是 :  www.fklinu

Windows下字符编码报错

报错如下
Traceback (most recent call last):
  File "c:/Users/86186/Desktop/python-exc/hello.py", line 3, in <module>
    str = fo.read(10);
UnicodeDecodeError: 'gbk' codec can't decode byte 0xff in position 0: illegal multibyte sequence    

解决方案
#!/usr/bin/python3.8
fo = open("foo.txt", "r+",encoding="utf-8",errors="ignore") # 如果在windows下读文件报错就加上编码和错误忽略,这里用的utf-8也不合适最后的结果会有问题每个字符之间都有个空格
str = fo.read(10);
print("读取的字符串是 : ", str)
fo.close() 

#!/usr/bin/python3.8
fo = open("foo.txt", "r+",encoding="utf-16",errors="ignore") # 查看记事本用的字符编码是utf-16,这样才能可以读到正确的内容
str = fo.read(10);
print("读取的字符串是 : ", str)
fo.close() 

image-20200923114720328

readline():返回字符串 读取下个行结束符之前的所有字节. 作为字符串返回整行,包括行结束符。 和 read() 相同, 它也有一个可选的 size 参数, 默认为 -1, 代表读至行结束符, 如果提供了该参数, 那么在超过 size 个字节后会返回不完整的行.

readlines():返回字符串列表 读取所有剩余的行,返回一个字符串列表。

三、写文件

使用方法write() writelines()

write() 可将任何字符串(包括二进制数据)写入一个打开的文件Linux下不会在字符串的结尾添加换行符('\n')windows是有换行符的不支持数字

writelines() 是针对列表的操作, 它接受一个字符串序列(字符串,字符串列表,字符串元组)作为参数, 将它们写入文件. 行结束符并不会被自动加入, 所以如果需要的话, 你必须在调用writelines()前给每行结尾加上行结束符.

语法: fileObject.write(string) string参数是要写入到已打开文件的内容。

#!/usr/bin/python3.8
fo = open("foo.txt", "w")
fo.write("www.fklinux.com!\nVery good site!\n"); 
fo.close() # 关闭打开的文件

被创建的foo.txt文件内容: 
# cat foo.txt 
www.fklinux.com!
Very good site!

四、文件指针

tell() 这个方法告诉我们文件内的当前位置,换句话说,下一次的读写会发生在文件开头这么多字节之后。

seek() 可以在文件中移动文件指针到不同的位置.

语法:

seek(offset [,from])

offset

表示要移动的字节数

from

指定开始移动字节的参考位置。 如果from被设为0这意味着将文件的开头作为移动字节的参考位置0 -- start of stream (the default); offset should be zero or positive

如果设为1则使用当前的位置作为参考位置python2可以python3必须带b读文件--rb 如果设为2那么该文件的末尾将作为参考位置 (其实唯一的作用是将指针移动到最后seek(0,2))

seek配置r+模式可以实现从指定位置修改文件

例: 就用上面创建的文件foo.txt

#!/usr/bin/python3.8
# python2没有任何问题python3 在seek的时候如果相对位置是1需要打开文件的是时候带'b'
fo = open("foo.txt", "r+")
str = fo.read(10);
print ("读取的字符串是 : ", str)

# 查找当前位置
position = fo.tell();
print ("当前文件位置 : ", position)

# 把指针再次重新定位到文件开头
position = fo.seek(0, 0);
str = fo.read(10);
print ("重新读取字符串 : ", str)
# 关闭打开的文件
fo.close()
 
输出结果 
     读取的字符串是 :  www.fklinu
     当前文件位置 :  10
     重新读取字符串 :  www.fklinu
        
In [77]: with open('a.txt','rb') as f:
    ...:     print(f.read(2))
    ...:     f.seek(2,1)  
    ...:     print(f.read(2))
    ...: 
b'he'
b'o '

五、文件迭代

一行一行访问文件很简单:

for eachLine in f:
    :

eachLine 代表文本文件的一行,包括末尾的行结束符 Python 2.2 中, 引进了迭代器和文件迭代, 文件对象成为了它们自己的迭代器用户不必调用 read() 方法就可以在 for 循环中迭代文件的每一行.

迭代器之前的老方法
for eachLine in f.readline():
    :
In [35]: for i in f: 
...:     	print(i,end="")    # end的作用是取消print默认的换行
...:                                                                          
    1111
    2222
    3333
    4444

print函数取消默认换行符

print 语句默认在输出内容末尾后加一个换行符, 而在语句后加一个逗号就可以避免这个行为. readline() 和 readlines() 函数不对行里的空白字符做任何处理,所以你有必要加上逗号. 如果你省略逗号, 那么显示出的文本每行后会有两个换行符, 其中一个是输入是附带的, 另个是 print 语句自动添加的.

打印输出的时候取消默认换行python2只需要在后面加一个逗号','

#!/usr/bin/env python
print "hello",   			# 在这里加逗号可以取消换行
print "hi"

#!/usr/bin/env python3.8
print("hello",end="")    	# 这是python3里的方法
print "hi"

另外也可使用迭代器的 next 方法, file.next() 可以用来读取文件的下一行.

注意python3已经不支持file.next()方法python3使用系统内置函数next()

和其它迭代器一样Python 也会在所有行迭代完成后引发 StopIteration 异常.

In [269]: f=open("a.txt","r+")

In [270]: next(f)
Out[270]: 'hello world\n'

In [271]: next(f)
Out[271]: 'nice to meet you\n'

In [272]: next(f)
Out[272]: 'I love you\n'

六、文件重命名和删除

创建普通文件

>> os.mknod("/a.txt")

Python的os模块提供了帮你执行文件处理操作的方法比如重命名和删除文件。

rename()方法 语法: os.rename(current_file_name, new_file_name)

例子: 下例将重命名一个已经存在的文件test1.txt。

#!/usr/bin/env python3.8
import os
os.rename("foo.txt","foo1.txt")          # 重命名文件foo.txt到foo1.txt

remove()方法 删除文件 语法: os.remove(file_name)

例子: 下例将删除一个已经存在的文件test2.txt。

#!/usr/bin/python3.8
import os
os.remove("test2.txt")  # 删除一个已经存在的文件test2.txt

七、Python目录操作

os模块有许多方法能帮你创建删除和更改目录。

mkdir()方法 语法: os.mkdir("newdir") 例子:

#!/usr/bin/python3.8
import os
os.mkdir("test")        # 创建目录test

chdir()方法 改变当前工作目录 语法: os.chdir("newdir") 例子: 下例将进入"/home/newdir"目录。

#!/usr/bin/python3.8
import os
os.chdir("/home/newdir")  # 将当前目录改为"/home/newdir"

getcwd()方法 显示当前工作目录 语法: os.getcwd() 例子:

#!/usr/bin/python
import os
os.getcwd()  # 给出当前的目录

rmdir()方法 删除目录,只能删除空目录 语法: os.rmdir('dirname') 例子:

#!/usr/bin/python3.8
import os
os.rmdir( "/tmp/test"  )  # 删除”"tmp/test"目录

八、Python File方法列表

file 对象使用 open 函数来创建,下表列出了 file 对象常用的方法

方法 描述
file.close() 关闭文件。关闭后文件不能再进行读写操作。
file.flush() 刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件, 而不是被动的等待输出缓冲区写入。
file.fileno() 返回一个整型的文件描述符(file descriptor FD 整型), 可以用在如os模块的read方法等一些底层操作上。
file.isatty() 如果文件连接到一个终端设备返回 True否则返回 False。
file.read([size]) 从文件读取指定的字节数,如果未给定或为负则读取所有。
file.readline([size]) 读取整行,包括 "\n" 字符。
file.readlines([sizehint]) 读取所有行并返回列表若给定sizeint>0返回总和大约为sizeint字节的行, 实际读取值可能比sizhint较大, 因为需要填充缓冲区。
file.seek(offset[,whence]) 设置文件当前位置
file.tell() 返回文件当前位置。
file.truncate([size]) 截取文件截取的字节通过size指定默认为当前文件位置。
file.write(str) 将字符串写入文件,没有返回值。
file.writelines(sequence) 向文件写入一个序列字符串列表,如果需要换行则要自己加入每行的换行符。

truncate() 方法 将文件截取到当前文件指针位置或者到给定 size , 以字节为单位. 它接受一个可选的 size 作为参数. 如果给定, 那么文件将被截取到最多 size 字节处. 如果没有传递 size 参数, 那么默认将截取到文件的当前位置.例如, 你刚打开了一个文件, 然后立即调用 truncate() 方法, 那么你的文件(内容)实际上被删除,这时候你是其实是从 0 字节开始截取的

In [19]: cat foo.txt
aaaaa
bbbbb

In [20]: f = open('foo.txt','r+')  #注意权限必须带+号

In [21]: f.truncate(2)
Out[21]: 2

In [22]: cat foo.txt
aa

八、Python序列化数据存储

序列化 (Serialization)是指将对象、数据结构的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

我们编写的程序,会涉及到各种各样的对象、数据结构,它们通常是以变量的形式在内存中存在着。当程序运行结束后,这些变量也就会被清理。但我们有时希望能够在下一次编写程序时恢复上一次的某个对象(如机器学习中的到结果,需要程序运行较长时间,多次运行时间成本太大),这就需要我们将变量进行持久化的存储。

一种方式是利用文件读写的方式将变量转化为某种形式的字符串写入文件内,但需要自己控制存储格式显得十分笨拙。更好的方式是通过序列化的方式将变量持久化至本地。

json是一种所有的语言都可以识别的数据结构。 如果我们将一个字典或者序列化成了一个json存在文件里那么java代码或者js代码也可以拿来用。 但是如果我们用pickle进行序列化其他语言就不能读懂这是什么了 所以如果你序列化的内容是列表或者字典非常推荐你使用json模块 但如果出于某种原因你不得不序列化其他的数据类型而未来你还会用python对这个数据进行反序列化的话那么就可以使用pickle

json序列化 json模块提供了四个功能 dumps 序列化对象至字符串,存储到内存

loads 对应的反序列化方法

dump 序列化对象至本地文件

load 对应的反序列化方法

pickle序列化 pickle模块提供了四个功能 dumps 序列化对象至字符串,存储到内存

loads 对应的反序列化方法

dump 序列化对象至本地文件

load 对应的反序列化方法

pickle可以存储什么类型的数据

  1. 所有python支持的原生类型布尔值整数浮点数复数字符串字节None。
  2. 由任何原生类型组成的列表,元组,字典和集合。
  3. 函数,类,类的实例

例子: pickle模块

>>> import pickle
>>> data = ['aa', 'bb', 'cc']  

dumps 存储数据将数据通过特殊的形式转换为只有python语言认识的字符串

>>> p_str = pickle.dumps(data)
>>> print(p_str) b'\x80\x03]q\x00(X\x02\x00\x00\x00aaq\x01X\x02\x00\x00\x00bbq\x02X\x02\x00\x00\x00ccq\x03e.

loads 读取数据将pickle数据转换为python的数据结构

>>> mes = pickle.loads(p_str)
>>> print(mes)
    ['aa', 'bb', 'cc']

dump 存储数据将数据通过特殊的形式转换为只有python语言认识的字符串并写入文件

with open('/tmp/aa.txt', 'wb') as f:
     pickle.dump(data, f)

注意以上代码中的w必须带b

load 读取数据从数据文件中读取数据并转换为python的数据结构

with open('/tmp/aa.txt', 'rb') as f:
     data = pickle.load(f)

注意以上代码中的r必须带b

写入多个对象

In [25]: import pickle
In [26]: li = [1,2,3]
In [27]: di = {'a':'b'}

In [28]:  with open('pickles.db','wb') as f:
    ...:     pickle.dump(li,f)
    ...:     pickle.dump(di,f)
    ...: 

读取多个对象

In [29]: with open('pickles.db','rb') as f:
    ...:     l2  = pickle.load(f)
    ...:     d2 = pickle.load(f)
    ...: 

In [30]: l2
Out[30]: [1, 2, 3]

In [31]: d2
Out[31]: {'a': 'b'}

在有序列化数据的基础上追加数据

In [2]: import pickle 
In [1]: d={"name":"fulei","age":28,"shengao":18}   # 第一次存储数据
In [3]: with open("a.txt","wb") as f: 
   ...:     pickle.dump(d,f) 

In [6]: e={"name":"fulei","age":28,"shengao":18}   

In [7]: with open("a.txt","ab") as f: 				# 第一次追加
   ...:     pickle.dump(e,f) 

In [9]: l={"xingming":"fulei","nianling":27,"gaodu":180}                       

In [10]: with open("a.txt","ab") as f:              # 第二次追加
    ...:     pickle.dump(l,f) 

In [13]: with open("a.txt","rb") as f:    			# 取数据时要按顺序取3次
    ...:     e=pickle.load(f) 
    ...:     t=pickle.load(f) 
    ...:     v=pickle.load(f) 
In [14]: e 
Out[14]: {'name': 'fulei', 'age': 28, 'shengao': 18}

In [15]: t 
Out[15]: {'name': 'fulei', 'age': 28, 'shengao': 18}

In [16]: v 
Out[16]: {'xingming': 'fulei', 'nianling': 27, 'gaodu': 180}

json模块

In [1]: import json

In [2]: s = json.dumps({"a": "b"})

In [3]: s
Out[3]: '{"a": "b"}'

In [4]: json.loads(s)
Out[4]: {'a': 'b'}
In [5]: import json

In [6]: li = [1,2,3]

In [7]: di = {'a':'b'}

In [8]: with open('json.db','w') as f:
   ...:     json.dump(li,f)  	#序列化存储列表'li'到文件中
   ...: 

In [9]: !cat json.db  			#json序列化的文件是直接可读的
[1, 2, 3]

In [15]: with open('json.db','r') as f:
    ...:     l2 = json.load(f) 	#反序列化
    ...: 

In [16]: l2
Out[16]: [1, 2, 3]

序列化多个对象到文件中

In [20]: with open('json.db','w') as f:
    ...:      f.write(json.dumps(li)+'\n') 	# 需要换行符保证每个对象占一行
    ...:      f.write(json.dumps(di)+'\n') 

从一个文件读取多个序列化对象时需用 loads 配合readline一次读一行

In [21]: with open('json.db','r') as f:
    ...:     l2 = json.loads(f.readline())
    ...:     d2 = json.loads(f.readline())
    ...: 

In [22]: l2
Out[22]: [1, 2, 3]

In [23]: d2
Out[23]: {'a': 'b'}

扩展练习

----编写程序,实现程序功能为修改指定文件内指定内容

[root@python python]# cat alterfile.py 
#!/usr/bin/env python3
# 程序执行方式python alterfile.py test.txt 旧关键字 新关键字
import sys
file_name = sys.argv[1]
old_str   = sys.argv[2]
new_str   = sys.argv[3]

def alter(file,old_str,new_str):
	file_data = ""
	with open(file,"r") as f:
		for line in f:
			if old_str in line:
				line = line.replace(old_str,new_str)
			file_data += line
	with open(file,"w") as f:
		f.write(file_data)
        
alter(file_name,old_str,new_str)

[root@python python]# cat test.txt
hello world
nice to meet you
nihao every body

[root@python python]# python alterfile.py test.txt world 世界

[root@python python]# cat test.txt
hello 世界
nice to meet you
nihao every body