1. 认识文件和I/O
文件是存储在设备上的一组字符或字节序列,可以包含任何内容,它是数据的集合和抽象。
文件包含两种类型:
- 文本文件:一般由单一特定编码的字符组成。
- 二进制文件:直接由二进制0和1组成,文件内部数据的组织格式与文件用途有关。
主要区别在于是否有统一的字符编码;都可以使用文本文件方式和二进制文件方式打开,但打开后的操作不同。采用文本方式打开文件,文件经过编码形成字符串;采用二进制方式打开文件,文件被解析为字节流。
I/O
在计算机中是指 Input/Output
,也就是Stream(流)的输入和输出。输入和输出是相对于内存来说的,Input Stream(输入流)是指数据从外(磁盘、网络)流进内存,Output Stream(输出流)是数据从内存流出到外面(磁盘、网络)。当程序运行时,数据都是在内存中驻留,由CPU这个超快的计算核心执行,涉及数据交换的地方就需要I/O接口,如磁盘操作、网络操作等。
文件描述符是读写文件时请求操作系统打开的一个对象,也就是程序中要操作的文件对象。
不同编程语言读写文件的操作步骤大体一样:
- 打开文件,获取文件描述符。
- 操作文件描述符,如读/写。
- 关闭文件。
文件读写操作完成后,应该及时关闭。一方面,文件对象占用操作系统的资源;另一方面,操作系统对同一时间打开的文件描述符的数量时有限制的,如果不及时关闭文件,可能会造成数据丢失。因为将数据写入文件时,操作系统不会立刻把数据写入磁盘,而是先把数据放到缓冲区异步写入磁盘。当调用close()
方法时,操作系统保证把没有写入磁盘的数据全部写到磁盘上,否则可能丢失数据。
除了内置函数open()
外,还可以通过如下途径对本地文件系统进行操作。
os
模块:是一个与操作系统进行交互的接口,可以直接对操作系统进行操作,直接调用操作系统的可执行文件、命令。os模块包含文件系统和进程管理。os.path
模块:提供一些与路径相关的操作函数。
stat
模块:能解析os.stat()
、os.fstat()
、os.lstat()
等函数返回的对象的学习,也就是能获取文件的系统状态信息(文件属性)
import os # 导入os模块 from os import path # 从os模块中导入path子模块
2. 打开文件
使用内置函数open()
可以打开文件,如果指定文件不存在,则创建文件,如果该文件无法打开,会抛出OSError。
fileObj = open(fileName,mode='r',buffering=-1,encoding=None, errors=None,newline=None,closedfd=True,opener=None)
open()函数共包含8个参数,除了第一个参数fileName必须设置外,其它参数都有默认值,可以省略。
fileName
:设置打开的文件名,包含所在路径,也可设置文件句柄。
mode
:打开模式,即操作权限,使用字符串表示,‘r’、‘w’、‘x’、‘b’可以和’b’、‘t’、‘+'组合使用,打开模式及其组合说明如下:
文件格式相关参数:可以与其它模式参数组合使用,用于指定打开文件的格式,需要根据文件类型进行选择。
- ‘t’:文本模式。默认以文本格式打开文件。一般用于文本文件。
- ‘b’:二进制模式。以二进制格式打开文件。一般用于非文本文件,如图片等。
通用读写模式相关参数:可以与文本格式参数组合使用,用于设置基本读、写操作权限,以及文件指针初始位置。
- ‘r’:只读模式。默认。以只读方式打开一个文件,文件指针被定位到文件头位置。如果文件不存在会报错。
- ‘w’:只写模式。打开一个文件只用于写入。如果文件已存在,则打开文件,清空文件内容,并把文件指针定位到文件头位置开始编辑;如果该文件不存在,则创建新文件,打开并编辑。
- ‘w’:追加模式。打开一个文件用于追加,仅有只写权限,无权读操作。如果该文件已存在,文件指针被定位到文件尾位置。新内容被写入到原内容之后;如果该文件不存在,创建新文件并写入。
特殊读写模式相关参数:
- ‘+’:更新模式。打开一个文件进行更新,具有可读、可写权限。注意该模式不能单独使用,需要与r、w、a模式组合使用。打开文件后,文件指针的位置由r、w、a组合模式觉得。
- ‘+’:新写模式。新建一个文件,打开并写入内容,如果该文件已存在则报错。
组合模式:文件格式与通用读写模式可以组合使用,另外,通过组合+模式可以为只读、只写模式增加写、读的权限。
r 模式组合:
- ‘r+’:文本格式读写。以文本格式打开一个文件用于读、写。文件指针被定位到文件头的位置。新写入的内容被覆盖原有文件部分或全部内容;如果文件不存在则报错。
- ‘rb’:二进制格式只读。以二进制格式打开一个文件,只能够读取。文件指针被定位到文件头的位置。一般用于非文本文件,如图片等。
- ‘rb+’:二进制格式读写。以二进制格式打开一个文件用于读、写。文件指针被定位到文件头位置。新写入的内容将覆盖原有文件部分或全部内容;如果文件不存在则报错。一般用于非文本文件。
w 模式组合
- ‘w+’:文本格式写读。以文本格式打开一个文件用于写、读。如果该文件已存在,则打开文件,清空原有内容,进入编辑模式;如果该文件不存在,则创建新文件,打开并执行写、读操作。‘w+’:二进制格式只写。以二进制格式打开一个文件,只能够写入。如果该文件已存在,则打开文件,清空原有内容,进入编辑模式;如果该文件不存在,则创建新文件,打开并执行只写操作。一般用于非文本文件。
- ‘wb+’:二进制格式写读。以二进制格式打开一个文件用于写、读。如果该文件已存在,则打开文件,清空原有内容,进入编辑模式;如果该文件不存在,创建新文件,打开并执行写、读操作。一般用于非文本文件。
a 模式组合
- ‘a+’:文本格式读写。以文本格式打开一个文件用于读、写。如果文件已存在,则打开文件,文件指针被定位到文件尾位置,新写入的内容添加在原有内容的后面;如果该文件不存在,则创建新文件,打开并执行写、读操作。
- ‘ab’:二进制格式只写。以二进制格式打开一个文件用于追加写入。如果该文件已存在,则打开文件,文件指针被定位到文件尾位置,新写入的内容在原有内容的后面;如果该文件不存在,创建新文件,打开并执行只写操作。
- ‘ab+’:二进制格式读写。以二进制格式打开一个文件用于追加写入。如果该文件已存在,则打开文件,文件指针被定位到文件尾位置,新写入的内容在原有内容的后面;如果该文件不存在,创建新文件,打开并执行写、读操作。
buffering
:设置缓冲方式。0表示不缓冲,直接写入磁盘;1表示行缓冲,缓冲区碰到\n换行符时写入磁盘;如果为大于1的正整数,则缓冲区文件大小达到该数字大小时写入磁盘;如果为负值,则缓冲区的缓冲大小为系统默认。encoding
:指定文件的编码方式,该参数只在文本模式下使用。errors
:报错级别。newline
:设置换行符(仅适用于文本模式)。
closefd
:布尔值,默认为True,表示fileName参数为文件名(字符串型);如果为False,则fileName参数为文件描述符。opener
:传递可调用对象。
# 新建一个test.txt文件,内容包含‘中国’,执行以下命令以文本文件和二进制文件打开 f = open('test.txt','rt',encoding='utf-8') # t表示文本格式方式 print(f.readline()) # 输出:中国 f.close() # 文件使用结束后要关闭,释放文件的使用授权 f = open('test.txt','rb') # b表示二进制格式方式 print(f.readline()) # 输出:b'\xe4\xb8\xad\xe5\x9b\xbd' f.close() # 关闭文件
在异常处理语句中打开
使用异常处理机制打开文件的方法:在try
语句块中调用open()
函数,在except
语句块中妥善处理文件操作异常,在finally
语句块中关闭文件。
fileName = 'test1.txt' # 创建的文件名 try: fp = open(fileName,'w+') # 创建文件,w+ 模式打开文件,如果不存在,则创建,不会抛出异常 print('%s 文件创建成功'%fileName) # 提示创建成功 except IOError: print('文件创建失败,%s 文件不存在'%fileName) # 提示创建失败 finally: fp.close() # 关闭文件 # 输出:test1.txt 文件创建成功 fileName = 'test2.txt' try: fp = open(fileName,'r') # r 模式只能打开已存在的文件,当打开不存在的文件时,抛出异常 except IOError: print('文件创建失败,%s 文件不存在'%fileName) # 提示创建失败 finally: fp.close() # 关闭文件 # 输出:文件创建失败,test2.txt 文件不存在
在上下文管理中打开
with
语句时一种上下文管理协议,是文件操作的通用结构。它能简化try…except…finally
异常处理机制的流程,语法和示例如下:
''' with open(文件) as file对象: 操作file对象 ''' with open('test.txt','r',encoding='utf-8') as file: # 打开文件 for line in file.readlines(): # 迭代每行字符串 print(line) # 打印每一行字符串
3.读取文件
使用file对象的readline()
、readlines()
或read()
方法可以读取文件的内容。
file.read(size=-1)
:从文件中读取整个文件内容。参数可选,如果给出,读取前size长度的字符串或字节流。file.readline(size=-1)
:从文件中读取一行内容,包含换行符。参数可选,如果给出,读取该行前size长度的字符串或字节流。file.readlines(hint=-1)
:从文件中读取所有行,以每行为元素形成一个列表。参数可选,如果给出,读取hint行。
file.seek(offset[,whence])
:改变当前文件操作指针的位置,参数offset表示需要移动偏移的字节数;参数whence表示偏移参照点,默认值为0,表示文件的开头;当值为1时,表示当前位置;当值为2时,表示文件的结尾。
f = open('test.txt','r',encoding='utf-8') s = f.read() print(s) # 显示内容 print(f.tell()) # 获取文件对象的当前指针位置 f.seek(0) # 改变指针到文件头位置 print(f.tell()) # 查看指针位置 ls = f.readlines() # 读取 print(ls) # 打印内容,列表存储:['北京\n', '人生苦短,我爱python\n', '我是five'] f.close() # 关闭
file对象内部记录文件指针的位置,以便下次操作。只要file对象没执行close()
方法,文件指针就不会释放。
使用for
和while
循环遍历文件
# for f = open('test.txt','r',encoding='utf-8') for line in f: print(line) f.close() # while f = open('test.txt','r',encoding='utf-8') while True: # 执行无限循环 line = f.readline() # 读取每行文本 if line: # 如果不是尾行,则显示读取的文本 print(line) else: # 如果时尾行,则跳出循环 break f.close() # 关闭
3. 写入文件
使用文件对象的write()
和writelines()
方法可以为文件写入内容。
f.write(s)
:向文件写入一个字符串或字节流,并返回写入的字符长度。f.writelines(lines)
:将一个元素为字符串的列表写入文件。
writelines()
方法不会换行写入每个元素,如果换行写入每个元素,就需要手动添加换行符\n。使用该方法写文件的速度更快。如果需要写入文件的字符串非常多,可以使用writelines()
方法提高效率;如果只需要写入少量的字符串,直接使用write()
方法即可。
ls = [ ['指标','2014年','2015年','2016年'], ['居民消费价格指数','102','101.4','102'], ['食品','103.1','102.3','104.6'], ['烟酒及用品','994','102.1','101.5'], ['衣着','102.4','102.7','101.4'], ['家庭设备用品','101.2','101','100.5'], ['医疗保健和个人用品','101.3','102','101.1'], ['交通和通信','99.9','98.3','98.7'], ['游乐教育文化','101.9','101.4','101.6'], ['居住','102','100.7','101.6'], ] f = open('cpi.csv','w',encoding='utf-8') # w 模式,存在文件,则清空内容,不存在则新建 for row in ls: f.write(','.join(row)+'\n') f.close()
逗号分隔的存储格式叫做CSV格式,常见通用的相对简单的文件格式,一维数据保存成CSV格式后,各元素采用逗号分隔,形成一行。二维数据由一维数据组成,每一行时一维数据,整个CSV文件时一个二维数据
# 转换为二维列表对象,再进一步打印成表格形状 f = open('cpi.csv','r') # r 默认打开文本文件 ls = [] # 定义空列表 for line in f: ls.append(line.strip('\n').split(',')) # 去除换行符 逗号分隔符分隔 f.close() # 关闭 print(ls) # 打印数据列表 print('-'*100) # 分隔区分 for row in ls: # 遍历列表 line = '' # 定义空字符串 for item in row: line += '{:10}\t'.format(item) # 指定宽度格式化打印 print(line) # 遍历打印
4. 删除文件
删除文件需要使用os模块,调用os.remove()
方法可以删除指定的文件。
在删除文件之前需要先检测文件是否存在。如果文件不存在,直接进行删除操作,将抛出异常。调用os.path.exists()方法可以检测指定的文件是否存在。
import os # 导入os模块 f = 'test2.txt' # 指定要操作的文件 if os.path.exists(f): # 判断文件是否存在 os.remove(f) # 存在则删除文件 print('%s 文件删除成功'% f) # 提示删除成功 else: # 不存在则提示 print('%s 文件不存在'% f)
5. 复制文件
文件对象没提供直接复制文件的方法,但是可以使用read()
和write()
方法,可以间接实现复制文件的操作。
music_name = '火车日记.mp3' # 定义文件名 with open(music_name,'rb') as music: # 以字节流方式打开文件,赋予读权限 new_name = 'a.mp3' # 定义复制后的文件名 with open(new_name,'wb') as new_music: # 以字节方式打开文件,赋予写权限 buffer = 1024 # 定义一次读1024字节 while True: # 循环读取 content = music.read(buffer) # 读取内容 if not content: # 当文件读取结束 break # 跳出循环 new_music.write(content) # 写内容
shutil 模块是另一个文件、目录的管理接口,提供了一些用于复制文件、目录的方法。其中,copyfile()方法可以实现文件的复制,copyfile(src,dst),该方法把src指向的文件复制到dst指向的文件。参数src表示源文件的路径,参数dst表示目标文件的路径,两个参数都是字符串类型。
6. 重命名文件
使用os
模块的rename()
方法可以对文件或目录进行重命名。
'.'
表示当前目录,os.listdir()
方法能够返回指定目录包含的文件和子目录的名字列表。
import os path = os.listdir('.') # 获取当前目录下所有文件或文件夹名称列表 print(path) # 显示列表:['a.mp3', 'cpi.csv', 'test1.txt', '火车日记.mp3', 'test.txt', 'open_file.ipynb'] if 'test1.txt' in path: # 如果存在test1.txt os.rename('test1.txt','test2.txt') # 将test1.txt改名为test2.txt elif 'test2.txt' in path: # 如果存在test2.txt os.rename('test2.txt','test1.txt') # 将test2.txt改为test1.txt
在实际中,通常需要把某一类文件修改为另一种类型,即修改文件的扩展名。
# 把扩展名为htm的文件修改为html import os path = os.listdir('.') # 获取当前目录下所有文件或目录名称列表 for filename in path: # 遍历当前目录下所有文件 pos = filename .find('.') # 获取文件扩展名前的点号下标位置 if filename[pos+1:] == 'htm': # 如果文件扩展名为htm newname = filename[:pos+1] + 'html' # 定义新的文件名,改扩展名为html os.rename(filename,newname) # 重命名文件
7. 文件查找和替换
文件内容的搜索和替换可以结合字符串查找和替换来实现。
# 新建一个文件filtered_words.txt,包含'程序员、北京朝阳、上海'内容 def filterwords(file_name): # 定义敏感词过滤函数 with open(file_name,'r') as f: content = f.read() word_list = content.split('\n') # 将文件内容转换成列表格式 print('脱敏前:',word_list) text = input('敏感词过滤:') # 输入测试内容 new_word_list = [] # 定义一个新列表 for word in word_list: # 遍历敏感词列表 if text in word: # 测试内含敏感词 length = len(text) # 获取敏感词长度 word = word.replace(text,'*'*length) # 用*替换敏感词 new_word_list.append(word) # 内容追加 return '脱敏后:'+str(new_word_list) # 返回脱敏后内容 file = 'filtered_words.txt' print(filterwords(file)) ''' 脱敏前: ['程序员、北京朝阳、上海'] 脱敏后:['程序员、**朝阳、上海'] '''
新建一个test3.txt文件,内容如下图,然后从中查找字符串’five’,并统计出现的次数;替换其中的’five’字符串为’python’
# 查找 import re # 导入正则模块 f1 = open('test3.txt','r') # 以只读模式打开文件 count = 0 # 定义计数变量 for s in f1.readlines(): # 读取文件每一行字符串,然后迭代 li = re.findall('five',s) # 在每行字符串搜索字符串'five' if len(li) > 0: # 如果字符串长度大于0,说明存在指定字符串 count = count + li.count('five') # 累计求和出现次数 print('查找到',str(count),'个five') # 输出显示字符串出现次数 f1.close() # 关闭 # 输出:查找到 9 个five # 替换 f1 = open('test3.txt') f2 = open('test4.txt','w') for i in f1.readlines(): f2.write(i.replace('five','python')) # 替换 f1.close() f2.close()