应用程序运行过程中产生的数据最先都是存放于内存中的,若想永久保存下来,必须要保存于硬盘中。应用程序若想操作硬件必须通过操作系统,而文件就是操作系统提供给应用程序来操作硬盘的虚拟概念。
用户或应用程序对文件的操作,就是向操作系统发起调用,然后由操作系统完成对硬盘的具体操作。
控制文件读写内容的模式
强调t
和b
不能单独使用,必须跟r/w/a
连用
文本模式(默认模式)
文本模式顾名思义存放的就是文本文件,使用t进行读取会默认将硬盘中的二进制转成字符(如果最初不是由字符转换成的二进制,会导致编码报错)
二进制(默认编码/encoding指定) <-> t控制转换为Unicode <-> 字符
-
操作的只能是文本文件
-
文本文件的读写都是以str(unicode)为单位
# 硬盘文件c内容默认格式(例如utf-8),而t模式要求读取为unicode模式 f = open('c',mode='rt') res = f.read() # 即读取时候进行转码 utf-8 -解码-> unicode # 这里解码默认为操作系统的默认的解码方式 ## linux和mac系统默认编码是utf-8 ,windows系统默认为gbk
如果不指定,就会导致乱码问题(调用文件内容不是英文)
-
必须为指定
encoding='编码'
# encoding = 'utf-8' open('aaa.txt',mode='rt',encoding='utf-8') as f1
二进制模式
在打开是图片、视频、软件的时候,我们不能使用t模式,报错提示该文件不是gbk编码,所以解码失败
# 错误演示 - t模式只能读取文本
with open(r'C:\Users\kinght\Videos\123.mkv',mode='rt') as f:
# open 只是创建文件句柄,所以不会出错
f.read() # 'gbk' codec can't decode byte 0xa3 in position 4: illegal multibyte sequence
# 硬盘二进制读入内存 -> t模式会将其进行decode解码(非文本会报错)
而b模式,就是硬盘读入内存,不做任何转换,可以针对所有的文件类型进行操作
而会将输出整合bytes类型 但其实就是二进制,以后操作bytes类型直接当成二进制使用,python自动转成二进制
注意:由于b模式哪怕打开的是文本文件,但是由于模式限定并不会做任何转换,所以也不能指定encoding参数
# 错误演示
# with open('user',mode='rb',encoding='utf-8') as f:
# 不能指定encoding参数
with open('user',mode='rb') as f:
res = f.read()
print(type(res))
print(res) # 显示的十六进制
由于他没做任何的转码操作,所以这里取出来的二进制是系统对应的二进制直接取出(详情回看编码那一章节),这里的user文件时直接在pycharm生成,所以是utf-8格式
b模式读写文本文件
读:手工解码转成字符
其实t模式就是一种b模式针对文本文件加解码的简洁模式
with open('user',mode='rb') as f:
res = f.read().decode('utf-8')
print(res)
写:字符编码转成二进制
with open('user',mode='wb') as f:
f.write("你好呀!".encode('utf-8')) # 需要自行编码
文件拷本案例
src_file = input('源文件路径>>>:').strip()
dst_file = input('目的文件路径>>>:').strip()
with open(r'{}'.format(src_file),mode='rb') as f1,\
open(r'{}'.format(dst_file),mode='wb') as f2:
# f2.write(f1.read()) # 如果数据量太大会冲爆内存
for line in f1:
f2.write(line)
循环的文件读取
for循环:以行为单位,如果单行过长,会导致一次性读取内容数据量过大
如文件拷贝案例一样,无论是文本文件、视频、图片还是其他的文件,for循环均是以换行符作为分隔(图片、视频、软件都有换行符)
with open(r"D:\ide\pycharm\PyCharm 2020.1.2\bin\pycharm64.exe",mode='rb') as f1:
for line in f1:
print(line)
注意:有些行由于过长占用了续行,每个b’才是一个真正的行
while循环:可自行控制单次读取数据量
while循环由于不能直接按行读取,所以需要指定往后读的字节数,如果文件单行过长,建议使用while循环
with open(r"D:\ide\pycharm\PyCharm 2020.1.2\bin\pycharm64.exe",mode='rb') as f1:
while True:
# 可以规定一次读几个字节
res = f1.read(1024) # 往后读1024个字节
if not res:
break
else:
print(res) # 如果res不为0 返回res长度
文件操作基本流程
1.打开文件
win平台 - 当路径分隔符与转义冲突的解决方案
# r = rawstring 原生字符串 - 不经过任何转义
open(r'E:\code\python\demo\first\Include\aaa.txt')
# 左斜杠 - 系统会自动识别左斜杠为路径符(linux平台直接就是左斜杠)
open('E:/code/python/demo/first/Include/aaa.txt')
open 有返回值
应用程序向操作系统的文件系统发起系统调用open(…)请求,操作系统找到对应一块硬盘空间,并返回一个文件对象赋值给一个变量f
# f的值是一种变量,占用本应用程序的内存空间
f = open('aaa.txt',mode='rt') # 默认控制文件操作模式为r 默认控制文件读写模式为t 在这里写很鸡肋,但是为了更直观
print(f) # <_io.TextIOWrapper name='aaa.txt' mode='r' encoding='cp936'> - 新的数据类型,叫做文件类型
2.操作文件:读/写
应用程序对文件的读写请求都是在向操作系统发起系统调用,然后再用操作系统控制数据读入内存或者写入硬盘
# 读文件内容
res = f.read()
print(res) # 123 - aaa.txt中的内容
res = f.
3.关闭文件
f.close() # 回收操作系统打开的文件资源
del f # 回收应用程序级的变量
这个先后顺序不能改变,如果先回收变量,就无法再使用变量名.close()回收文件资源了
甚至说由于python解释器会自动对变量进行内存管理,可以不写del f
f.close() # 回收操作系统打开的文件资源
# 只关闭了操作系统的文件资源,而不解除变量,变量还在,但是不能进行文件操作了
print(f) # <_io.TextIOWrapper name='aaa.txt' mode='rt' encoding='cp936'>
print(f.read()) # ValueError: I/O operation on closed file.
但是系统规定了能够打开文件的数量,python解释器不能自动控制回收操作系统,所以f.close()
还是很重要的
# linux系统中使用ulimit -u可查看能够同时打开的文件数
操作系统会在一定时间范围内扫描文件,如果文件系统没有了使用,也会自动关闭文件,但是这个期间内所导致的性能损耗也是非常大的
with语句
with open(…) as …语句,在子代码块完成后自动运行f.close
回收文件资源
# 小贴士:除去windows系统外,其它系统均不会以后缀名作为区分文件类型的方式
# 文件对象 又称为 文件句柄 : 其实就是控制文件的
with open('aaa.txt',mode='rt') as f1:
res = f1.read()
print(res) # aaaa
# with的子代码块运行完成后,会自动运行f.close()
# with 同时打开多个文件
# 加上\代表下一行还属于这一行 只是为了美观进行的换行
with open('aaa.txt',mode='rt',encoding='utf-8') as f1,\
open('bbb',mode='rt') as f2:
res1 = f1.read() # res1文件内容为中文由于是windows平台,需要指定utf-8
res2 = f2.read()
print(res1,res2) # 哈哈 bbbb
文件读写的模式
所有演示均以t模式为操作模式
r模式(只读模式)
基础知识
# 当文件不存在时
# with open('d.txt',mode='rt',encoding='utf-8') as f:
# ... # 会报错
# 当文件存在时
with open('aaa.txt',mode='rt',encoding='utf-8') as f:
print('第一次读取'.center(50,'*'))
# f.read 从硬盘把文件指针内容从头读到结尾 - 一次性全读
# 如果文件过大会导致占用内存过多
res = f.read()
print(res)
print('第二次读取'.center(50,'*'))
# 第二次读取是不会有内容的,因为with open把文件打开后,r模式让文件指针在最前面,所以可以读取
# 但是经过一次的.red之后,文件指针已经在最末尾了,所以不能再次读取
res1 = f.read()
print(res1)
修改登陆案例
新建user文件存放账号密码
第一次修改文件
user文件内容
kinght:geekxk
# coding:utf-8
username = input("请输入账号:").strip()
passwrod = input('请输入密码:').strip()
with open('user',mode='rt',encoding='utf-8') as users:
res = users.read()
uname,upasswd = res.split(':') # 解压赋值
if username == uname and passwrod == upasswd:
print("登陆成功")
else:
print("账号或密码错误")
但绝大多数情况账号和密码都不是唯一的
第二次修改
修改user内容
kinght:geekxk
root:root
abc:abc
tom:jack
注意,文件中每一行是存在换行符的\n
,而且print也会再次复制换行符
with open('user',mode='rt',encoding='utf-8') as f:
for lin in f:
print(lin)
完整版修改:
input_username = input("请输入账号:").strip()
input_passwd = input('请输入密码:').strip()
# 验证
with open('user',mode='rt',encoding='utf-8') as f:
for lin in f:
# strip 是消除所有的空白字符(包括换行符)
username,password = lin.strip().split(':')
if username == input_username and password == input_passwd:
print('login successfull')
break
else:
print('username or password error')
w模式(只写模式)
w模式用来创建全新的文件
基础知识
当文件不存在的时候会生成一个空文件(未设置文件类型)
# 当文件不存在时,会生成文件
with open('d',mode='wt',encoding='utf-8') as f:
... # python的占位符号
当文件存在时候会清空文件(坚决杜绝W模式打开重要文件)
# 当文件存在会清空文件,指针位于开始位置
with open('user',mode='wt',encoding='utf-8') as f:
... # python的占位符号
with open('user',mode='wt',encoding='utf-8') as f:
# f.read() # 只写模式不能读
f.write('哈哈哈哈,我擦嘞\n干啥勒')
# 在w模式下打开文件没有关闭的情况下,会让指针继续往后书写
f.write('猜猜这是第几行') # 并没有换行符
f.write('再来一次')
文本文件的拷贝案例
# coding:utf-8
# 文本文件的copy工具
input_file = input("请输入源文件路径>>>:")
output_file = input('请输入输出文件存放路径>>>:')
# 不直接使用变量名而使用标准输出语法是因为可能会有windows系统使用本程序,需要处理反斜杠
with open(r'{}'.format(input_file),mode='rt',encoding='utf-8') as f1,\
open(r'{}'.format(output_file),mode='wt',encoding='utf-8') as f2:
# f2.write(f1.read()) # 如果数据量太大会冲爆内存
for line in f1:
f2.write(line)
a模式(只追加写)
a模式用来在原有文件的基础上写入新的内容,比如记录日志,比如注册功能
基础知识
当文件不存在时,会创建空文档,文件指针再开头
# 当文件不存在时,会创建空文档
with open('c',mode='at',encoding='utf-8') as f:
...
当文件存在时,文件指针会直接跳到末尾
# 当文件不存在时,会创建空文档
with open('user',mode='at',encoding='utf-8') as f:
f.write('\n新写入的内容')
f.write('\n在写一行')
# a模式,文件不会被清空,文件指针会直接跳到末尾
with open('user',mode='at',encoding='utf-8') as f:
f.write('\n文件不会被清空')
注册案例
# coding:utf-8
inp_username = input('请输入注册账号:')
inp_passworld = input('请输入密码:')
with open('user',mode='at',encoding='utf-8') as users,\
open('user',mode='rt',encoding='utf-8') as xy_user:
# 读取user内的值,并查找是否用相同用户名
for xy in xy_user:
username,passworld = xy.strip().split(':')
if username == inp_username:
print("账号已存在")
break
else:
users.write('{}:{}\n'.format(inp_username,inp_passworld))
+模式
+模式不能单独使用,必须配合r、w、a
案例假设 asdasd文件
kinght:abcd
root:root
abcd:123
cda:we
r+模式
可读可写,但是特性取决于r(即文件不存在则报错),文件存在指针在最前面
with open('asdasd',mode='rt+',encoding='utf-8') as f:
f.write('abc')
kin被替换成了abc
w+模式
可读可写,但特性取决于w(即文件不存在创建文件),文件指针在最前面
with open('asdasd',mode='wt+',encoding='utf-8') as f:
# w模式会清空文件
f.write('abc') # 写入文件后
# write会让指针移到末尾,故.read直接读取都不到东西
# .read读取文件是从当前文件指针位置到末尾
print(f.read())
a+模式
可读可写,但特性取决于a(即文件不存在创建文本文件),文件指针在最后面
with open('asdasd',mode='at+',encoding='utf-8') as f:
# a模式会让文件指针直接在最后
# 故.read直接读取不到东西
print(f.read())
x模式
只写模式(了解即可),文件不存在则生成文件,文件存在则报错,这个模式可以防止w的覆盖操作,不过完全可以加判断代替
# 文件存在则报错
with open('a.txt',mode='xt',encoding='utf-8') as f:
pass # pass 与 ... 作用相同
# [Errno 17] File exists: 'a.txt'
# 文件不存在则生成文件
with open('b.txt',mode='xt',encoding='utf-8') as f:
f.write('哈哈哈哈')
操作文件的其他方法
读操作
read和readlines由于都会一次性读取所有的文件内容,所以对于内存有一定溢出的危险
f.read()
读取所有内容,执行完该操作后,文件指针会移动到文件末尾
with open('user',mode='rt',encoding='utf-8') as f:
print(f.read(3)) # 指定单次读取3字符(b模式是3个bytes数据)
print(f.read()) # 读取完所有内容
f.readline()
读取一行内容,光标移动到第二行首部
# 读操作
with open('user',mode='rt',encoding='utf-8') as f:
# 读取一行内容,光标移动到第二行首部
print(f.readline()) # 你好呀!
print(f.readline()) # helloworld
print(f.readline()) # i'm fine thank you
while文件拷贝案例修改
with open('user',mode='rt',encoding='utf-8') as f:
while True:
line = f.readline()
if len(line) == 0:
break
else:
print(line)
f.readlines()
当前文件指针为起始,读取每一行内容,并且按行为分割,存放于列表中
with open('user',mode='rt',encoding='utf-8') as f:
print(f.readlines()) # ['helloworld\n', "i'm fine thank you\n"]
写操作
f.write
with open('user',mode='wt',encoding='utf-8') as f:
f.write('111\n222\n333\n')
f.writelines
将列表循环写入到文件中
方案一
l = ['111\n','222','333']
with open('user',mode='wt',encoding='utf-8') as f:
for line in l:
f.write(line)
方案二
l = ['444\n','555','666']
with open('user',mode='wt',encoding='utf-8') as f:
f.writelines(l)
f.flush(不推荐执行,通常出现在测试场景)
刷新,写入文件的时候为了减少I/O延迟,通常不是即使写入硬盘,而是先放于内存中,f.flush则是让计算机立即将前面的操作写入到硬盘中
with open('user',mode='wt',encoding='utf-8') as f:
f.write('abcdefg')
f.flush()
其他的相关操作
f = open('user',mode='rt',encoding='utf-8')
print(f.readable()) # True 文件是否可读
print(f.writable()) # False 文件是否可写(这里是r模式,不可写)
print(f.closed) # False 文件是否关闭
print(f.encoding) # utf-8 文件编码格式(如果文件打开模式为b,则没有该属性)
print(f.flush()) # 立刻将文件内容从内存刷到硬盘
print(f.name) # 文件名
Bytes类型的使用
t模式写入,要求列表内必须全部为字符串
l = ['444','555','666',777]
with open('user',mode='wt',encoding='utf-8') as f:
f.writelines(l)
我们知道,b模式需要编码才能写入
l = [
'44asd4',
'55weq5',
'66qwe6'
]
with open('user',mode='wb') as f:
f.writelines(l)
编码后
l = [
'44asd4'.encode('utf-8'),
'55weq5'.encode('utf-8'),
'66qwe6'.encode('utf-8')
]
with open('user',mode='wb') as f:
f.writelines(l)
如果是字符串中只有英文和数字,bytes类型的英文和数字只是在前方加了b
l = [
b'44asd4',
b'55weq5',
b'66qwe6'
]
with open('user',mode='wb') as f:
f.writelines(l)
而有了中文,bytes则需要规定编码格式encode('utf-8')
,实际上转换是调用bytes进行类型转换,规定格式utf-8
bytes('上',encoding='utf-8')
将中文转换为bytes类型
控制文件指针操作
前文提到过,我们写入和读取都会让文件指针移动,导致在同一文件句柄下无法重复读取和覆盖写入
指针移动的单位都是以bytes/字节为单位
f.tell() # 获取文件指针当前位置
f.seek(n,模式)
n指的是移动bytes个数,模式有三种(0,1,2),只有0模式可以在t下使用,1、2必须在b模式下用
如果指针移动到末尾就不会再次移动
0:参照物文件开头的位置
# user文件内容:123你好
f = open('user',mode='rb')
f.seek(3,0) # 文件指针在第3个bytes
print(f.tell()) # 3
f.seek(2,0) # 文件指针在第2个bytes
print(f.tell()) # 2 注意:这里是参照文件开头
# 如果指针移动到中文3个字节,会导致转码失败
f.seek(4,0) # 第四个字节是中文’你‘的第一个字节
print(f.read().decode('utf-8')) # 由于不完整读取不到第一个字节,转码会导致报错
1:参照物当前文件指针位置
f = open('user',mode='rb')
f.seek(3,0) # 文件指针在第3个bytes
print(f.tell()) # 3
f.seek(2,1) # 文件指针在第5个bytes
print(f.tell()) # 5 注意:这里是参照文件指针当前位置
2:参照物是文件末尾,应该倒着移动(负数为倒着移)
# user:123你好
f = open('user',mode='rb')
f.seek(-3,2)
print(f.tell()) # 6
print(f.read().decode('utf-8')) # 好
仿tail -f
程序
import time
# 使用b模式是为了增加适用范围
with open('access.log',mode='rb') as f:
# 指针移动到文件末尾
f.seek(0,2) # 通常seek的1和2模式不能在t模式下使用,但0移动除外
while True:
res = f.readline().decode('utf-8')
if len(res) == 0:
time.sleep(1) # 让程序沉睡1秒
else:
print(res,end='')
修改文件内容
# 文件a.txt内容如下
张一蛋 山东 179 49 12344234523
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
# 执行操作
with open('a.txt',mode='r+t',encoding='utf-8') as f:
f.seek(9)
f.write('<妇女主任>')
# 文件修改后的内容如下
张一蛋<妇女主任> 179 49 12344234523
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
# 强调:
# 1、硬盘空间是无法修改的,硬盘中数据的更新都是用新内容覆盖旧内容
# 2、内存中的数据是可以修改的
文件对应的是硬盘空间,硬盘不能修改对应着文件本质也不能修改
文件的内容可以修改大致的思路是计算机将硬盘中文件内容读入内存,然后在内存中修改完毕后再覆盖回硬盘
方案1
目前文件编辑器大多采用此方案
with open('a.txt',mode='rt',encoding='utf-8') as f:
# 将文件整体读入内存
res = f.read()
# 修改文件
data = res.replace('张一蛋','张全蛋')
print(data)
# 此时修改后的文件在内存中,就可以打开另一个句柄
with open('a.txt',mode='wt',encoding='utf-8') as f:
f.write(data) # 将修改后的内容写入硬盘
实现思路:将文件内容发一次性全部读入内存,然后在内存中修改完毕后再覆盖写回原文件
优点: 在文件修改过程中同一份数据只有一份
缺点: 会过多地占用内存
方案2
import os
with open('a.txt',mode='rt',encoding='utf-8') as f,\
open('.a.txt.swap',mode='wt',encoding='utf-8') as f1:
# .a.txt.swap 是一种命名规范 .是隐藏文件 .swap是缓存文件
for line in f:
f1.write(line.replace('李二蛋','牛铁蛋'))
os.remove('a.txt') # 删除源文件
os.rename('.a.txt.swap','a.txt') # 将中间文件文件名改为源文件
实现思路:以读的方式打开原文件,以写的方式打开一个临时文件,一行行读取原文件内容,修改完后写入临时文件…,删掉原文件,将临时文件重命名原文件名
优点: 不会占用过多的内存
缺点: 在文件修改过程中同一份数据存了两份
补充的知识
在最初的时候,换行是由两个字符共同完成的操作
'\r\n'
\r
完成回到行首,\n
完成到达下一行
f.read()
# 修改文件
data = res.replace(‘张一蛋’,‘张全蛋’)
print(data)
此时修改后的文件在内存中,就可以打开另一个句柄
with open(‘a.txt’,mode=‘wt’,encoding=‘utf-8’) as f:
f.write(data) # 将修改后的内容写入硬盘
实现思路:将文件内容发一次性全部读入内存,然后在内存中修改完毕后再覆盖写回原文件
优点: 在文件修改过程中同一份数据只有一份
缺点: 会过多地占用内存
## 方案2
```python
import os
with open('a.txt',mode='rt',encoding='utf-8') as f,\
open('.a.txt.swap',mode='wt',encoding='utf-8') as f1:
# .a.txt.swap 是一种命名规范 .是隐藏文件 .swap是缓存文件
for line in f:
f1.write(line.replace('李二蛋','牛铁蛋'))
os.remove('a.txt') # 删除源文件
os.rename('.a.txt.swap','a.txt') # 将中间文件文件名改为源文件
实现思路:以读的方式打开原文件,以写的方式打开一个临时文件,一行行读取原文件内容,修改完后写入临时文件…,删掉原文件,将临时文件重命名原文件名
优点: 不会占用过多的内存
缺点: 在文件修改过程中同一份数据存了两份
补充的知识
在最初的时候,换行是由两个字符共同完成的操作
'\r\n'
\r
完成回到行首,\n
完成到达下一行
于是乎操作系统对于这个就有了不同的理解,例如linux和mac平台认为直接使用\n
来代替会到行首加下一行的工作即可,而win平台则坚持使用了\r\n
的方式,不过对于python3而言,我们编写代码直接使用\n
就可以了