字符编码
1 字符编码分类
1.1 ASCII码
1. 只支持英文字符串和特殊字符
2. 采用8位二进制数(8个bit位)表示一个英文字符串或特殊字符, 最多可以表示256个英文字符和特殊字符
1.2 GBK编码
1. 支持英文字符, 特殊字符, 中文字符
2. 采用1个byte, 8位二进制来表示一个英文字符串
3. 采用2个bytes, 16位二进制来表示一个中文字符串
1.3 Unicode
1. 现阶段, 计算机内存中, 统一使用Unicode编码, 所有的字符格式, 都可以和Unicode互转.
2. ASCII, GBK和utf-8等编码, 表示的是字符从内存往硬盘存和从硬盘读取到内存时是怎么转换的
比如, 使用ASCII, 那么需要在编辑器选择ASCII编码, 这样编辑文本时, 无论输入任何语言的字符, 都不会乱码, 因为输入是先输入到内存, 内存统一使用unicode
而打开文本时, 也要选择用ASCII编码打开, 这样才不会出现乱码.
3. 兼容万国字符, 与每个国家的文字都有对应关系
4. 采用2个bytes, 16个bits位来表示一个中文字符串, 个别生僻字符会采用4bytes或者8bytes
1.4 utf-8
1. 支持万国字符
2. 1个bytes表示英文, 3个bytes表示中文, 生僻字符会用4个bytes表示
2 人类字符, 内存字符存储以及硬盘字符存储的对应关系
2.1 存数据阶段

2.2 取数据阶段

内存固定使用unicode,我们可以改变的是存入硬盘和读取文本时采用的格式.
目前, 无论是存和取, 都应该使用utf-8的格式
3 文本文件存取乱码问题
文本文件存取乱码问题
存乱了:解决方法是,文本编辑器的编码格式应该设置成支持文件内字符的格式
取乱了:解决方法是,文件是以什么编码格式存入硬盘的,就应该以什么编码格式读入内存,在文本编辑器中设置编码格式和存盘格式一致
4 解决Python解释器读文件时不乱码
Python解释器默认读文件的编码
Python3默认:utf-8
Python2默认:ASCII
指定文件头修改默认的编码:
在py文件的首行写:
# coding:gbk
通过文件头来告诉Python解释器用哪种编码来读取文件
而对于文件头, 解释器会用自己默认的编码读取. 因为无论ASCII还是utf-8都可以读取英文, 因此, 文件头解释器会靠默认的编码去读取
保证运行Python程序前两个阶段不乱码的核心法则:
指定文件头
# coding:文件当初存入硬盘时所采用的编码格式
文件是哪种编码格式存到硬盘的, Python解释器就该用哪种编码格式去读取
读取格式可以在文件中通过文件头来指定, 而存到硬盘的格式是取决于文本编辑器的设置
5 Python解释器识别语法时如何不乱码
识别语法时, 是在内存执行的, 因此, Python文件读取到内存后, 使用什么格式编码就会影响语法分析
内存中都是统一的unicode格式, 因此, 文件读取到内存后, 也要存成unicode模式
Python3的str类型在内存默认直接存成unicode格式,无论如何都不会乱码
保证Python2的str类型不乱码
x=u'上' # 强制Python2将字符串类型存成unicode, Python2的中文字符串要加上u'字符串', 而英文可以不加, 但最好加上
- 通过Python2执行, 如果中文字符串不加u, 会乱码
# coding:utf-8
x = '上'
print(x)

- 通过Python2执行, 给中文字符串加上u, 则不会乱码
# coding:utf-8
x = u'上'
print(x)

Python2解释器有两种字符串类型:str、unicode
# str类型
x='上' # 字符串的值会按照文件头指定的编码格式存入变量值的内存空间
# unicode类型
x=u'上' # 强制存成unicode
6 编码与解码
编码指的是从unicode --> utf-8
解码指的是从uft-8 ---> unicode
# coding: utf-8
x = "哈哈哈"
res1 = x.encode("gbk") # 将unicode格式, 编码成gbk
print(res1) # b'\xb9\xfe\xb9\xfe\xb9\xfe'
print(type(res1)) # <class 'bytes'> 字符转换成字节类型, Python解释器并不会把bytes类型转成人类字符, 所以只会显示bytes类型
res2 = res1.decode("gbk") # 将gbk格式, 解码为unicode
print(res2) # 哈哈哈 # 解码成unicode, 解释器会把unicode自动转成人类字符, 所以print会显示人类字符
# 编码与解码通常在新老平台对接时使用, 老平台如果用的是Python2, 那么新平台在于其传数据时, 就要涉及编码和解码
7 总结:
Python3: 用pycharm, 无需任何操作, pycharm默认使用utf-8存, 而python3解释器, 默认用utf-8. 对于字符串, python3都直接存成unicode
Python2: 文件头, 用utf-8, 字符串, 用u'字符串'
解决2和3版本兼容性:
1. 所有的代码, 都指定文件头, # coding: utf-8
2. 所有的代码, 字符串都用u"字符串"
3. 所有的代码都存成utf-8格式
编码encode: 人类字符 --> Unicode(内存) --> uft-8,gbk,ascii(硬盘bytes类型)
解码decode: uft-8,gbk,ascii(硬盘bytes类型) --> Unicode(内存) --> 人类字符
内存数据如果需要存到本地或者远程硬盘, 需要经过encode编码, 转成utf-8, 然后存到硬盘, encode后, 得到bytes类型
文件操作部分
1、什么是文件
文件是操作系统提供给用户/应用程序操作硬盘的一种虚拟的概念/接口
用户/应用程序(open())
操作系统(文件)
计算机硬件(硬盘)
2、为何要用文件
用户/应用程序可以通过文件将数据永久保存到硬盘中
即操作文件就是操作硬盘
用户/应用程序直接操作的是文件,对文件进行的所有的操作,都是
在向操作系统发送系统调用,然后再由操作系统将其转换成具体的硬盘操作
3、open()功能的模式介绍
3.1 控制文件读写内容的模式:t和b
注意: t和b不能单独使用,必须跟r/w/a连用
- t文本(默认的模式, 如果指定了某种读写操作模式, 但没有指定读写内容模式, 默认就是t模式)
1. 读写都以str(unicode)为单位的. t模式自动把读入内存的字符根据指定的解码编码格式转成Unicode, 把写入磁盘的文本从Unicode转成指定的编码格式二进制
2. 文本文件, 只有文本文件才涉及字符编码, 因此, open()的数据来源必须是文件.
3. 因为Python中, 数据在内存是存成unicode格式, 必须指定encoding='utf-8', 把内存中的str(unicode), 编码成utf-8, 存到硬盘.
- b二进制/bytes
3.2 控制文件读写操作的模式
- r只读模式, 默认的操作模式
- w只写模式
- a只追加写模式
- +:r+、w+、a+
4 文件操作基本流程
4.1 打开文件
# windows路径分隔符问题, \a,\n等在Python中都是特殊的转义字符, 如果在文件路径中存在会被当做转义
open('C:\a.txt\nb\c\d.txt')
# 解决方案一:推荐, 在文件路径前加r(rawstring), 表示使用原生字符串, 不会转义
open(r'C:\a.txt\nb\c\d.txt')
# 解决方案二: 用'/'表示路径分隔符, open()功能会自动识别转换
open('C:/a.txt/nb/c/d.txt')
f=open(r'C:\Users\David\PycharmProjects\pythonProject\s14\day11\练习.py',mode='rt')
# f的值是一种变量,占用的是Python应用程序的内存空间
print(f)
>> <_io.TextIOWrapper name='C:\\Users\\David\\PycharmProjects\\pythonProject\\s14\\day11\\练习.py' mode='rt' encoding='cp1252'>
open()除了创建一个变量值占用Python解释器的内存空间,
还会向操作系统请求打开文件, 而操作系统会将打开的文件映射到硬盘空间.
所以, 通过应用程序对文件进行操作时, 先是由应用程序向操作系统发起读或写请求,
再由操作系统将请求转换为磁盘上的操作.
站在变量角度, f=open(), open()的返回值赋予给f, 会占用应用程序的内存
站在文件角度, f=open()会打开文件, 打开的文件是由操作系统维护, 因此占用操作系统的内存
x=int(10) # 并不涉及文件的概念, 只是纯内存的操作
代开文件路径两种方式:
绝对路径: open('C:\a.txt\nb\c\d.txt')
相对路径: open('d.txt'), 要求打开的文件, 和Python程序代码文件在同级的目录下
4.2 操作文件
读/写文件,应用程序对文件的读写请求都是在向操作系统发送系统调用,然后由操作系统控制硬盘把输入读入内存、或者写入硬盘
f=open(r'C:\Users\David\PycharmProjects\pythonProject\s14\day11\练习.py',mode='rt', encoding='utf-8')
# open()一个文件, 会把文件对象赋值给f,
res=f.read() # f.read()会通过文件对象, 把内容从硬盘读到内存
# read()是从文件指针当前位置开始往后读
print(type(res))
>> <class 'str'>
print(res)
>>
f=open(r'C:\Users\David\PycharmProjects\pythonProject\s14\day11\练习.py',mode='rt', encoding='utf-8')
res=f.read() # f.read()就是把变量f赋的文件内容从硬盘读出来到内存, 赋值给res
print(res)
4.3 关闭文件
f=open(r'C:\Users\David\PycharmProjects\pythonProject\s14\day11\练习.py',mode='rt', encoding='utf-8')
res=f.read()
f.close() # 回收操作系统资源, 因为操作系统能同时打开的文件数是有限的. 如果不手动关闭文件, 操作系统会把长时间没有用的文件关闭, 但是在没关闭之前, 文件虽然没人在用, 但是文件依然处于打开状态.
print(f) # 回收了操作系统资源后, f变量还是存在的
>> <_io.TextIOWrapper name='C:\\Users\\David\\PycharmProjects\\pythonProject\\s14\\day11\\练习.py' mode='rt' encoding='utf-8'>
f.read() # 变量f存在,但是不能再读了, 这时f剩的只是文件对象, 而真正的文件已经被f.close()了
res = f.read()
print(res)
>> res = f.read()
ValueError: I/O operation on closed file.
del f # 回收应用程序资源, 变量一般不需要手动删除, 因为Python的垃圾回收机制会做这件事
# 一定是先回收操作系统的资源, 再回收内存资源, 如果先把内存资源的变量名f回收了, 那么f.close()就找不到f了

4.4 with上下文管理
- 准备两个文件, a.txt, b.txt. a.txt存aaaa, b.txt存bbbb
# 打开文本文件a.txt, 使用读模式, 并且赋值给f1.
# 注意: with 不能直接把open()的结果通过"="赋值给变量, 而是用as f1
# with会自动执行f1.close(), 在with子代码块运行完后, 关闭文件
with open('a.txt', mode = 'rt') as f1:
res = f1.read()
print(res)
>> aaaa
# with可以同时打开多个文件
with open('a.txt', mode = 'rt') as f1, open('b.txt', mode = 'rt') as f2:
res1 = f1.read()
res2 = f2.read()
print(res1) >> aaaa
print(res2) >> bbbb
with open(r'a.txt', mode='rt') as f1, \
open(r"b.txt", mode='rt') as f2:
res1 = f1.read()
res2 = f2.read()
print(res1)
print(res2)
- open()一个文件, 得到的返回值, 是文件对象, 也可以叫文件句柄, 可以理解为对真正的磁盘上的文件的一种引用, 用来控制文件
4.5 指定字符编码
- 创建一个新文件, c.txt, 存汉字, 哈哈哈哈
- 创建文件后, c.txt在硬盘上是utf-8格式的二进制, 因为Pycham默认用utf-8
with open('c.txt', mode = 'rt') as f1:
res1 = f1.read() # f1.read()本质是从磁盘中读取原本存的格式的二进制, 这里就是utf-8格式的二进制. 之后, open()中的t模式, 会强制把读入到内存的内容, 解码为Unicode, 因此, 需要指定解码的编码格式
print(res1)
>> 哈哈哈哈
res1 = f1.read()
# 执行f1.read(), 会把目标文件, 从硬盘读入到内存
# 文件c.txt在硬盘是utf-8格式的, 读入到内存后也是utf-8格式
# 但是, 因为读取的是文本文件,是t模式, 所以在内存中要求读写的字符串都是unicode格式, 因此t模式会将read()读入的utf-8解码成unicode
# 解码要按照文件存成的字符编码去解码
# 但是, 如果命令中没有告诉open()用哪种字符编码格式进行解码操作, 那么open()会用操作系统默认的字符编码,去解码
# 没有指定encoding参数, 操作系统会使用自己默认的字符编码, 去解码
# Linux, Mac, utf-8
# Windows, 默认要看语言设置, 在dos命令行通过chcp命令查看活动代码页, 然后和活动代码页对照
# 因此, 如果操作系统默认不是utf-8, 那么解码的时候就是用另一个字符编码去解码utf-8, 就会出现乱码
with open('c.txt', mode = 'rt', encoding='utf-8') as f1: # 告诉open()用utf-8去解码
res1 = f1.read()
print(res1, type(res1))
>> 哈哈哈哈
# encoding='utf-8'就是规定, 无论编码还是解码, 都用utf-8的字符编码去执行. 文本读入到内存,用utf-8去解码, 写入到磁盘用utf-8去编码
5 文本模式(t模式)下的文件读写操作模式
5.1 r模式
- 默认的操作模式
- 只读模式
# 当open()要读的文件不存在时, r模式会报错, 找不到文件或目录
# 当open()要读的文件存在时, r模式打开文件时, 文件指针跳到最开始位置
with open('c.txt', mode = 'rt', encoding='utf-8') as f1:
res = f1.read()
# f1.read()在没有指定任何参数的情况下, 会把文件内容, 从当前指针的位置一下读到结尾
# r模式打开文件时, 指针默认就是在文件最开始, 所以执行f1.read()的结果就是, 一次性把文件全部的内容, 都读入到硬盘. 因此读大文件时, 会造成内存被文件大量占用
print(res)
>> 哈哈哈哈
- r模式小问题
with open('c.txt', mode = 'rt', encoding='utf-8') as f1:
print("第一次读".center(50,'*'))
res = f1.read()
print(res)
print("第二次读".center(50,'*'))
res = f1.read()
print(res)
>>
***********************第一次读***********************
哈哈哈哈
***********************第二次读***********************
# 第二次读c.txt文件时, 并没有结果返回, 这是因此,r模式会一次性从文件开始读到结尾, 读完, 文件指针就停在了文件结尾
# 而由于第一次读完, 文件没有关闭就开始读第二次, 那么第二次读时就是从文件末尾开始读, 那肯定是没有内容的, 所以没有返回值
with open('c.txt', mode = 'rt', encoding='utf-8') as f1:
print("第一次读".center(50,'*'))
res = f1.read()
print(res)
with open('c.txt', mode='rt', encoding='utf-8') as f1:
print("第二次读".center(50,'*'))
res = f1.read()
print(res)
>>
***********************第一次读***********************
哈哈哈哈
***********************第二次读***********************
哈哈哈哈
# 这时, 第一次读完c.txt, 返回哈哈哈哈后, 再次打开c.txt, 由于文件是存在的, 那么r模式下, 文件指针会移动到文件最开始, 从最开始一直读到结尾, 最后返回哈哈哈哈
补充: 如果文件中有特殊字符, 比如换行, 那么r模式也会读取并返回
- r模式案例: 利用with open()读取文本中的用户名和密码, 和用户输入的用户名和密码作比较, 如果相等, 返回登录成功, 不相等, 返回登录失败
- 先创建一个文本文件, 保存用户名和密码
# 注意, 文件不要有多余的空行和空格,如果有, 那么r模式读文件时, 会把空行和空格都读出来, 然后用split()去做分隔, 会造成数据错误
user.txt
admin:password
#版本1: 仅实现单用户的验证
_username = input("username: ").strip() # 将用户输入用strip()处理, 去除左右的空格
_password = input("password: ").strip()
with open("user.txt", mode='rt',encoding='utf-8') as f:
info = f.read()
username, password = info.split(':') # 将info中的数据, 用split()以":"做分隔, 分隔后的结果是个列表, 然后将列表解压, 把值赋给对应的变量
if _username == username and _password == password:
print("登录成功")
else:
print("登录失败")
#版本2: 实现多用户验证
# 先在user.txt中添加多个用户名和密码
admin:password
admin1:password1
admin2:password2
admin3:password3
********代码*********
_username = input("username: ").strip()
_password = input("password: ").strip()
with open("user.txt", mode='rt', encoding='utf-8') as f:
for line in f: # 这里不能用info = f.read(), 然后用for循环去遍历info, 因为info里存的是字符串, for循环遍历字符串会打印每一个字符
# 打开文件后, 得到一个f的文件对象, 之后就是利用相应的功能去操作这个f对象, f.read()就是读, 而用for循环就是遍历这个文件对象, 以每行为一次for循环单位
# print(line,end='')
# 这里因为本身文本每一个结尾都是换行符, 而且print默认会在结尾加换行符, 所以, 需要用end='', 把print结尾的换行符转行成空, 只留文本中每一行末尾的换行符
# username, password = line.split(':')
# 这里也不能直接使用split()去做分隔, 因为, 上面print(line,end='')的结果的每一行后面都是有换行符\n的
# 如果这里直接用split(), 那么得到的结果就是 admin, password\n, 这样去和用户输入作比较就会出问题
# info = line.strip('\n').split(':')
# 使用strip去除每行最后的换行符, strip去掉的是字符左右的空白字符, 包括空格, 换行符, 等. 这样info中存的就是一个个列表, 由用户名和密码组成
username, password = line.strip('\n').split(':') # 这样就从文件中, 得到了所有的用户名和密码对应关系
if _username == username and _password == password:
print("登录成功")
break
else: # 这里的else一定是作用在for循环, 而不是if条件判断, 因为需要把整个文件都遍历完, 发现没有符合的账号密码, 才返回登录失败
print("登录失败")
# 不过这时, 如果登录的账号正好在文件的最后一行, 那么需要把整个文件每一个都遍历一遍, 如果账号密码信息众多, 会有效率问题, 之后可以考虑, 把用户名和密码转成字典格式, 如果输入的用户名在整个文本里存在, 那么就根据用户名作为key去取密码, 然后判断密码是否相等, 如果用户名本身就不存在, 那么直接返回登录失败
_username = input("请输入用户名: ").strip()
_password = input("请输入密码: ").strip()
d1 = dict()
with open(r'user.txt', mode='rt', encoding='utf-8') as f:
for line in f:
res = line.strip('\n').split(':')
# print(res)
d1[res[0]] = res[1]
# print(d1)
for i in range(len(d1)):
if _username in d1:
if _password == d1[_username]:
print("登录成功")
break
else:
print("用户名或密码错误")
5.2 w模式
- 只写模式
- t模式下, 只能写字符串内容, b模式下, 写bytes类型
# 当文件不存在时, 会创建空文件
with open('d.txt', mode = 'wt', encoding= 'utf-8') as f:
pass

# 当文件存在时, w模式会清空文件
d.txt
哈哈哈哈
啦啦啦啦
with open('d.txt', mode = 'wt', encoding= 'utf-8') as f:
pass
>> 清空文件, 然后指针位于文件开始位置
- 只写, 不能读, 不能使用read()
with open('d.txt', mode = 'wt', encoding= 'utf-8') as f:
f.read()
>> io.UnsupportedOperation: not readable
- 写数据, 用write()
with open('d.txt', mode = 'wt', encoding= 'utf-8') as f:
f.write("hahaha")
# 会把write()中的数据写入到文件, 并且指针处于行尾, 而且默认是不换行的

# 如果想要换行, 在写入的内容后加换行符即可'\n'
with open('d.txt', mode = 'wt', encoding= 'utf-8') as f:
f.write("hahaha\n")
# 需要注意的是, 此时, 再次写入数据, 因为文件是存在的, 所以会清空文件, 再把新的内容写进去, 所以, w模式不支持追加, 而是每次新输入都把源文件的数据清空,文件指针放到首位, 把新的内容写进去

补充1: w模式, 在没有关闭文件的情况下, 连续写入数据, 新数据会接着上一次文件指针的位置, 接着写入, 并不会清空源文件
with open('d.txt', mode = 'wt', encoding= 'utf-8') as f:
f.write("ha\n")
f.write("haha\n")
f.write("haha\ha\n")
# 这时, 文件会依次写入数据
ha
haha
haha\ha
补充2: w模式, 每次关闭文件后, 连续写入, 那么每次都会把文件先清空, 文件指针回到文件开头, 然后写入数据, 因此, 对于重要的文件, 千万不要用w模式打开写入
5.3 a模式
-只追加写, 不能读
# 在文件不存在时, 会创建空文件, 文件指针处于文件开始位置
with open('e.txt', mode = 'at', encoding= 'utf-8') as f:
pass

# 在文件存在时, 打开文件后, 会把文件指针移动到文件末尾, 然后写入新的数据
with open('e.txt', mode = 'at', encoding= 'utf-8') as f:
f.write("哈哈哈哈")
# 如果文件指针始终处于一行的结尾, 并且新写入的数据没有换行符, 那么数据就一直在同一行追加
# 在文件打开不关时, 连续追加数据时, 和w模式下, 不关文件连续追加数据一样, 都是在文件指针末尾追加数据
a模式, 每次打开文件时, 不会清空文件, 因为它是把文件指针移动到文件结尾, 然后写入数据
w模式, 每次打开文件时, 都会清空文件, 因为它是每次都把文件的指针移动到文件开始的位置, 所以会清空原有内容
补充: w和a模式的使用场景
w模式因为每次打开都会清空文件, 因此, 适用于创建新文件的场景
a模式因为每次打开不会清空文件, 而是追加写文件, 适用于日志文件, 等需要记录数据并且追加数据的场景
- a模式案例: 账号注册功能
_username = input("请输入用户名: ")
_password = input("请输入密码: ")
with open("user_register.txt", mode = 'at', encoding = "utf-8") as f:
f.write("{username}:{password}\n".format(username=_username, password=_password))
# 这样每次运行, 都会把用户输入的用户名和密码, 以固定的格式存到"user_register.txt"里, 实现账号信息持久保存
admin1:password1
admin2:password2
admin3:password3
- w模式案例: 实现文本文件复制
# 思路1:
with open("user_register.txt", mode = 'rt', encoding = "utf-8") as f1:
info = f1.read() # 先把整个文件都读到内存, 赋值给info变量
# print(info)
with open("user_register_backup.txt", mode = 'wt', encoding= "utf-8") as f2:
f2.write("{info}".format(info=info)) # 之后把info变量保存的内容全部写到新文件
# 思路2, 把读文件和写文件写到一条命令
with open("user_register.txt", mode = 'rt', encoding = "utf-8") as f1, \
open("user_register_backup.txt", mode = 'wt', encoding = "utf-8") as f2:
res = f1.read()
f2.write(res)
# 实现复制脚本
_src_file = input("请输入源文件的绝对路径: ").strip()
_dst_file = input("请输入目标文件的绝对路径: ").strip()
with open(r"{src_file}".format(src_file=_src_file), mode = 'rt', encoding = "utf-8") as f1, \
open(r"{dst_file}".format(dst_file=_dst_file), mode = 'wt', encoding = "utf-8") as f2:
res = f1.read()
f2.write(res)


# 利用for循环把每行数据读出来, 再写入到新文件
_src_file = input("请输入源文件的绝对路径: ").strip()
_dst_file = input("请输入目标文件的绝对路径: ").strip()
with open(r"{src_file}".format(src_file=_src_file), mode = 'rt', encoding = "utf-8") as f1, \
open(r"{dst_file}".format(dst_file=_dst_file), mode = 'wt', encoding = "utf-8") as f2:
for line in f1: # 使用for循环, 将f1的每一行读出来, 写到f2里, 写进去一行, 引用计数就为0, 会被解释器清空, 内存中永远只有一行数据, 对内存压力小
f2.write(line)
5.4 +模式
-
"+" 不能单独使用, 必须配合r, w, 或a模式一起使用
-
r+t:
r+模式处理文件, 特性与限制取决于r模式
r+模式处理文件时, 文件不存在, 会直接报错, 因为是以r为基础, r模式下, 打开不存在的文件就是直接报错
with open('g.txt', mode = 'r+t', encoding= 'utf-8') as f:
...
>> FileNotFoundError: [Errno 2] No such file or directory: 'g.txt'
注意: r+模式下, 如果源文件非空, 那么使用write()写数据时, 会从文件开始位置依次覆盖, 因为r模式打开文件时, 会把文件指针移动到文件最开始
- w+t
w+模式处理文件时, 如果文件不存在, 则按照w的特性, 会创建文件
with open('g.txt', mode = 'w+t', encoding= 'utf-8') as f:
...
>> 创建g.txt文件
w+模式处理文件时, 如果文件存在, 则按照w的特性, 会清空文件
with open('g.txt', mode = 'w+t', encoding= 'utf-8') as f:
...
# 先向g.txt写入一些数据, 然后执行with open(), 由于g.txt文件已经存在, 那么按照w的特性, 会把g.txt文件清空
注意: w+虽然可以读文件了, 但是只要打开文件, 内容就会被清空.
- a+t
a+模式处理文件是, 如果文件不存在, 则创建文件, 如果文件存在, 则把文件指针移动到文件最末端
w+和a+的特性:
- 在文件不关闭的情况下, 使用w+或者a+连续写入数据后, 如果接着想要在不关闭文件的情况下用read()读数据是读不出来的, 因为w+和a+写完数据后, 文件指针会停在文件末尾, 而read()是从文件指针的当前位置开始读, 因此永远是读不出来内容的
with open('g.txt', mode = 'w+t', encoding= 'utf-8') as f:
f.write("哈哈哈\n")
f.write("哈哈哈\n")
f.write("哈哈哈\n")
f.write("哈哈哈\n")
f.write("哈哈哈\n")
print(f.read())
>> 空, 但是g.txt是有内容的, a+模式效果一样, 写入数据后, 文件指针都是停在文件末尾, read()又是从文件指针当前位置开始读, 所以读不出来数据

- 对于已存在的文件:
a模式打开文件, 文件指针是在文件末尾的, 读不出来内容, w模式打开文件会清空, 所以也读不出来内容
总结
1、什么是文件
用户/应用程序(f=open(),获得文件对象/文件句柄)
操作系统(文件)
计算机硬件(硬盘)
2、为何要用文件
用于应用程序操作硬盘,永久保存数据,或者从硬盘读数据
3、如何用
f=open(r'C:\new_dir\a.txt',mode='r+t',encoding='utf-8')
# f.read()就是把硬盘的二进制数据, 读到内存, 可以赋值给变量, 拿到整个文件的内容, 并不涉及字符编码转换
# t会告诉open()功能将二进制转换成字符文本, 但前提是这些二进制数据, 原本就是由字符文本转换成的二进制
# 然后open()并不知道这些二进制是什么格式的二进制, 因此open()功能会用默认的字符编码也就是系统的字符编码去打开文件,把这些文件当成是系统的字符编码去打开, 因此在open()功能要指定encoding, 告诉open()功能用什么字符编码去解码打开文件
# f.read() 读出来的就是unicode, python打印出来时会转成utf-8
res=f.read() # 读出硬盘的二进制(utf-8)->t控制将二进制转换成unicode->字符
print(res)
# f.write()
f.close()
print(f) # f.close()把文件关闭后, f还是存在的, 因为f是python的一个变量, 但是f.read()就不存在了, 因为文件已经关闭了
f.read() # 抛出异常
with open(...) as f,open(...) as f1:
code1
code2
code3
4. open()打开文件是没有效率问题的, 因为只是产生一个文件句柄, 发起系统调用去打开文件, 并没有读和写操作, 只有读和写操作是涉及I/O. 真正的效率问题是出现在读和写阶段, 也就是f.read()和f.write()
5. 复制文件脚本
_src_file = input("请输入源文件的绝对路径: ").strip()
_dst_file = input("请输入目标文件的绝对路径: ").strip()
with open(r"{src_file}".format(src_file=_src_file), mode = 'rt', encoding = "utf-8") as f1, \
open(r"{dst_file}".format(dst_file=_dst_file), mode = 'wt', encoding = "utf-8") as f2:
#res = f1.read() # 这种方法, 会瞬间将文件内容全部读出来到内存, 赋值给res, 如果文件量大, 会占用大量内存空间
#f2.write(res)
for line in f1: # 使用for循环, 将f1的每一行读出来, 写到f2里, 写进去一行, 引用计数就为0, 会被解释器清空, 内存中永远只有一行数据, 对内存压力小, 但是要写的数据量是不变的
f2.write(line)
5.5 x模式
x模式(控制文件操作的模式): 只写模式, 不可读, 执行open()时, 如果文件存在则报错, 如果文件不存在则创建
with open(r'aa.txt', mode = 'xt', encoding ='utf-8') as f1:
pass

文件存在时, 则报错
FileExistsError: [Errno 17] File exists: 'aa.txt'
x模式的问题是, 只能在文件不存在的时候, 进行写操作, 一旦文件创建出来了, 打开就会报错, 也就无法后续修改
6 二进制模式(b模式)下的文件读写操作模式
b: binary模式
读写都是以bytes为单位, 不加解码格式, 读出来的都是b'内容'格式的bytes类型, 写入也是
可以针对所有文件
b模式下, 一定不能指定encoding参数, b模式和encoding没关系, 不做解码操作.
b模式更通用
b模式下, 执行f.read(), 会把硬盘内容读入内存, 但是不会做任何转换, 只是二进制. 而t模式转换成unicode, 然后有解释器转换成指定的解码类型
Python解释器会将这些二进制转换成bytes类型
# aaaa.txt
哈哈哈哈aaaa
with open(r'aaaa.txt', mode = 'rb') as f1:
res = f1.read() # b模式下, 会把文件直接从硬盘加载到内存, 不做任何转换, 文本如果是以utf-8存到硬盘, 那么b模式读到内存, 就是utf-8格式的二进制, 那么赋值给res打印后也是utf-8格式的二进制, Python解释器再把它们以16进制显示出来, 如果是英文字母就直接显示英文, 如果是中文则以16进制显示
print(res)
>> b'\xe5\x93\x88\xe5\x93\x88\xe5\x93\x88\xe5\x93\x88aaaa\r\n'
如果需要读取文本字符, 那么需要解码, 由utf-8解码成可读字符
print(res.decode('utf-8'))
>> 哈哈哈哈aaaa
b模式下, 写入字符数据, 要先把字符数据转成bytes类型, 才能用f.write()去写入
with open(r'f.txt', mode='wb') as f:
info = "哈哈哈aaa".encode('utf-8') # 用encode, 将输入的字符, 转成utf-8格式的二进制. 之后b模式读取的时候, 也要用utf-8.
f.write(info)
f.txt
哈哈哈aaa
6.1 b模式和t模式的对比
b模式更通用, 可以处理所有类型的文件, 包括文本文件, 视频, 图片等
t模式只能处理文件文件
在处理文本文件时, t模式更方式, 因为t模式会自动解码, 将读到内存中的二进制解码成可读字符
而如果使用b模式去处理文本文件, 还需要手动执行编码和解码操作
6.2 b模式案例
- b模式实现文件拷贝工具
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:
# res=f1.read() # 内存占用过大
# f2.write(res)
for line in f1: # 复制文件, 并不涉及显示文件内容, 只是把从源文件读到内存的二进制, 原封不动存到新的文件, 因此, 不涉及编码和解码
f2.write(line)
- 循环读取文件两种方式
- t模式for循环读取文本文件
对于文本文件来说, 区分不同的行的依据就是换行符/n
使用for循环对f文件句柄进行遍历时, 就是按照换行符取每一行数据
d.txt
哈哈哈哈
h=aaaa
with open(r'd.txt', mode='rt', encoding='utf-8') as f:
for line in f:
print(line,end='') # print会默认带换行符,因此要指定空字符结尾, 避免出现空行. 而文本文件每一行结尾的换行符如果要去掉, 需要用strip. 但是f只是文件句柄, 所以要先把line付给一个变量, 然后对变量进行strip操作. 这样就会把文本文件每行的换行符去掉
>>
哈哈哈哈
h=aaaa
- b模式循环读取任意格式的文件
b模式for循环读取任意格式的文件, 也会以换行符区分不同的行
方式一:自己控制每次读取的数据的数据量
with open(r'test.jpg',mode='rb') as f:
while True:
res=f.read(1024) # 1024字节, b模式读写都是字节为单位. 每次都是从当前位置往后读1024字节
if len(res) == 0:
break # 当长度为0时, 终止循环
print(len(res))
方式二:以行为单位读,当一行内容过长时会导致一次性读入内存的数据量过大, 此时应该使用while循环, 指定每次读的字节长度
with open(r'g.txt',mode='rt',encoding='utf-8') as f:
for line in f:
print(len(line),line)
with open(r'g.txt',mode='rb') as f:
for line in f:
print(line)
with open(r'test.jpg',mode='rb') as f:
for line in f:
print(line)
7 文件操作的其他方法
- 读相关操作
7.1 readline()
一次只读一行, 以换行符为分隔符
with open(r'a.txt', mode = 'rt', encoding = 'utf-8') as f1:
res1 = f1.readline()
res2 = f1.readline()
print(res1, end='')
print(res2, end='')
>>
嘿嘿嘿ccc
哦哦哦bbb
利用while循环
while True:
res = f1.readline()
if len(res) == 0:
break
print(res,end='')
>>
嘿嘿嘿ccc
哦哦哦bbb
哈哈哈aaa
7.2 readlines()
读多行, 并且以列表的形式返回
with open(r'a.txt', mode = 'rt', encoding = 'utf-8') as f1:
res = f1.readlines()
print(res)
>> ['嘿嘿嘿ccc\n', '哦哦哦bbb\n', '哈哈哈aaa']
f.read()与f.readlines()都是将内容一次性读入内存,如果内容过大会导致内存溢出,若想将内容全读入内存,要用循环
- 写相关操作
7.3 writelines()
write()本身就可以写入多行, 只要用\n换行符即可
with open(r'a.txt', mode = 'wt', encoding = 'utf-8') as f1:
f1.write('111\n222\n333')
>>
111
222
333
writelines()作用是: 将列表中的元素, 写入到文件里
with open(r'a.txt', mode = 'wt', encoding = 'utf-8') as f1:
l = ['111\n','222','333']
for line in l: # for循环会遍历列表中的每个元素, 然后再写入到文件里, 但是如果想把每个元素写到单独一行, 那么列表的元素需要有个'\n', 否则都会写到以一行
f1.write(line)
>>
111
222333
writelines()可以实现上面相同的功能
with open(r'a.txt', mode = 'wt', encoding = 'utf-8') as f1:
l = ['111\n','222','333']
f1.writelines(l)
>>
111
222333
- 补充: t模式下, 只能写入字符串数据, b模式写的是bytes类型

with open(r'h.txt', mode = 'wt', encoding='utf-8') as f1:
f1.write(str(444))
>>
444
用b模式写入
with open(r'a.txt', mode = 'wb') as f1:
l = ['111\n'.encode('utf-8'),'222'.encode('utf-8'),'333'.encode('utf-8')] # 需要对字符进行encode编码, encode是字符编码, 只对字符串有效, 对其他类型无效
f1.writelines(l)
>>
111
222333
纯英文字符或数字, 直接在字符前加b即可, 不用.encode()
因为所有的字符编码都是兼容英文和数字的, .encode()得到的结果就是b''
with open(r'a.txt', mode = 'wb') as f1:
l = [b'aaa',b'bbb',b'ccc',b'444']
f1.writelines(l)
>>
aaabbbccc444
'上'.encode('utf-8') 等同于bytes('上',encoding='utf-8')
7.4 flush()
将产生的数据立即写入磁盘
with open(r'h.txt', mode = 'wb') as f1:
f1.write(b'444') # f1.write()是将内存的数据提交给操作系统, 而操作系统多久写磁盘要看策略.
f1.flush() # 执行f1.flush()就是告诉操作系统, 立即写入磁盘
7.5 其他方法
readable() 判断文件是否可读
writeable() 判断文件是否可写
closed 判断文件是否已经被关闭
with open(r'h.txt', mode='wb') as f1:
print(f1.readable()) # False w模式下不可读
print(f1.writable()) # True w模式可写
print(f1.closed) # False with没执行完, 文件处于打开状态
print(f1.closed) # True with执行完, 文件自动关闭
8 文件指针移动
指针移动的单位都是以bytes/字节为单位
只有一种情况特殊:
t模式下的read(n),n代表的是字符个数
a.txt
aaa您好
with open(r'a.txt', mode = 'rt', encoding='utf-8') as f1:
res = f1.read(4) # read(n)以字符为单位读取
print(res)
>>
aaa您
seek() 用来控制文件指针的移动
f.seek(n,模式):n指的是移动的字节个数, 如果移动的字节个数, 会把字符分开, 那么就无法执行, 解释器会报错. 比如文件只存了一个汉字, 但是代码中写了移动2个字节, 这时就会报错
模式0:参照物是文件开头位置
f.seek(9,0) # 0模式参照文件开头, 9表示移动9个字节, 此时指针处于第九个字节的位置
f.seek(3,0) # 虽然此前执行了f.seek(9,0), 但是0模式是以文件开头为参照物, 所以再执行(3,0)还是从文件开头开始, 此时指针处于字节3
模式1:参照物是当前指针所在位置
f.seek(9,1) # 9 如果以r模式打开文件, 一开始指针处于文件开头, 移动9个字节, 此时处于9
f.seek(3,1) # 12 再执行(3,1), 以当前的9为参照物再移动3个字节, 因此, 此时处于12字节
模式2:参照物是文件末尾位置,应该倒着移动
假设文件一共12个字节
f.seek(-9,2) # 3
f.seek(-3,2) # 9
强调:只有0模式可以在t下使用,1、2必须在b模式下用
f.tell() 获取文件指针当前位置
8.1 案例
准备文件

with open(r'a.txt', mode = 'rb') as f1:
f1.seek(9,0)
print(f1.tell()) >> 9 # 0模式以文件开头为参照, 移动9个字节, 所以当前处于字节9
f1.seek(3,0)
print(f1.tell()) >> 3 # 0模式下, 指针再次回到文件开头, 移动3个字节, 当前处于字节3
with open(r'a.txt', mode='rb') as f1:
f1.seek(9,0)
res = f1.read() # 移动9个字节, 然后把后续文件都读出来
print(f1.tell()) # 读完全部文件, 指针位于文件末尾, 该文件共31个字节, 其中每行9个字节字符, 另外要算上第一行和第二行结尾的/r/n, 每个特殊符号占一个字节, 共4个字节, 加起来31
print(res)
>>
31
b'\r\nbbb\xe5\x93\x88\xe5\x93\x88\r\nccc\xe5\x93\xa6\xe5\x93\xa6' # 读到第九个字节, 指针位于您好后面, 之后就是第一行的/r/n.
当文件指针移动到某一个位置后, 执行解码操作, 那么解出来的是指针后面的字节
with open(r'a.txt', mode = 'rb') as f1:
f1.seek(3,0)
res = f1.read()
print(res.decode('utf-8'))
>>
您好
bbb哈哈
ccc哦哦
with open(r'a.txt', mode = 'rb') as f1:
f1.seek(-9,2)
print(f1.tell()) >> 22
f1.seek(-3,2)
print(f1.tell()) >> 28
补充:
得到bytes类型的三种方式:
bytes类型可以理解为二进制
1、字符串编码之后的结果为bytes类型
'上'.encode('utf-8')
bytes('上',encoding='utf-8')
2、b'必须是纯英文或数字字符'得到的结果为bytes类型
3、b模式下打开文件,f.read()读出的内容也是bytes类型
8.2 seek()应用, 实现tail -f功能
- 准备数据写入程序
input.py
with open(r'a.txt', mode = 'at', encoding ='utf-8') as f: # 输入一定要a模式, 追加写
f.write('哈哈哈\n')
- 准备tail -f程序
output.py
import time
with open(r'a.txt', mode = 'rb') as f: # 使用b模式, 更通用
# r模式, 每次打开文件, 指针都是位于文件开始, 而tail程序需要关注的是新的内容, 所以要实现打开文件后, 把指针移动到文件末尾
f.seek(0,2) # 2模式每次打开文件后, 将指针移动到文件末尾, 并且正向读0个字节, 这样只要文件没有输入数据, 那么每次循环后文件指针还是原地不动
while True:
output = f.readline() # 每次读完一行, 指针还是在文件末尾
if len(output) == 0:
time.sleep(0.5)
else:
print(output.decode('utf-8'),end='')
- 准备文件
a.txt
- 执行输出output.py程序
C:\python38\python.exe C:/Users/David/PycharmProjects/pythonProject/s14/day11/output.py
执行output.py后, 指针会停留在文件末尾, 不断的去循环读取a.txt文件, 一旦有内容进来, 就会输出
- 执行input.py, 查看output.py结果
C:\python38\python.exe C:/Users/David/PycharmProjects/pythonProject/s14/day11/output.py
哈哈哈
8.3 文件修改的两种方式
硬盘的数据都是存在扇区上的, 每次写数据, 都是将原有的数据覆盖.
当想在文件中修改或插入数据时, 会把旧的数据覆盖, 而不会把后续的内容向后移, 因为一旦移了一个字节, 那么后续所有的硬盘上的内容, 都要后移.
平时我们通过文本编辑器修改文件时, 实际上修改的是内存的数据, 因此打开文件后, 文本编辑器会通过系统调用把文件内容全部读入内存, 之后的修改都是修改内存的内容
修改后, 点击保存, 那么文件在磁盘上的内容, 会被内存中存储的新内容整体覆盖
注意: 修改文件内容, 一定是先把文件从硬盘读到内存, 在内存做修改, 然后写回硬盘
8.3.1 类文本编辑器式修改
准备文件
a.txt
哈哈哈
嘿嘿嘿
哦哦哦
with open(r'a.txt', mode = 'rt', encoding='utf-8') as f:
res = f.read() # 将文件读到内存, 赋值给res
data = res.replace('哈哈哈','hahaha') # 将res中的'哈哈哈'替换成'hahaha'再赋值给data
with open(r'a.txt', mode = 'wt', encoding='utf-8') as f1: # 利用w模式打开文件, 将文件清空, 重新把data的数据写入文件, 就完成了字符串的替换
f1.write(data)
# 此时, 一定要在读文件的with结束, 再以w模式打开文件, 如果r模式打开文件后, 紧接着w模式打开文件, 那么文件内容会被立即清空
利用这种方法, 每次做文件修改, 都是先w模式打开把文件清空, 再把要写的内容全部写入到文件
8.3.2 生成一个新文件, 将内容写到新文件, 然后删除旧文件
准备文件
a.txt
哈哈哈
嘿嘿嘿
哦哦哦
import os
with open(r'a.txt', mode = 'rt', encoding = 'utf-8') as f1, \ # 打开源文件
open(r'.a.txt.swap', mode = 'wt', encoding = 'utf-8') as f2: # 以w模式打开一个新的.swap临时文件
for line in f1: # 从源文件中读一行
f2.write(line.replace('哈哈哈','aaa')) # 将源文件中需要替换的字符做替换, 然后写入临时文件, 由于新的文件是以w模式开启, 并且一直没有关闭, 所以不会清空. 循环结束, 每一行的字符都会被替换
# 字符串.replace()当要替换的内容不存在时不会报错
os.remove('a.txt') # 修改完, 需要删除源文件
os.rename('.a.txt.swap','a.txt') # 然后将临时文件改名为源文件
aaa
嘿嘿嘿
哦哦哦
8.3.3 两种方式对比
第一种方式: 耗费内存空间, 因为read()是把数据一次性读入内容, 但是节省了硬盘空间, 因为硬盘上的数据只有一份
第二种方式: 耗费硬盘空间, 因为在文件修改期间, 需要生成临时文件, 但是内存空间是不浪费的, 每次都是读一行加载到内存, 做修改, 然后写入到新文件
网友评论