day04
字符编码
文本编辑器存取文件的原理
- 打开编辑器就打开了启动了一个进程,是在内存中的,所以,用编辑器编写的内容也都是存放与内存中的,断电后数据丢失。
- 要想永久保存,需要点击保存按钮:编辑器把内存的数据刷到了硬盘上。
- 在我们编写一个py文件(没有执行),跟编写其他文件没有任何区别,都只是在编写一堆字符而已。
Python解释器执行py文件的原理
- 第一阶段:Python解释器启动,此时就相当于启动了一个文本编辑器
- 第二阶段:Python解释器相当于文本编辑器,去打开test.py文件,从硬盘上将test.py的文件内容读入到内存中(小复习:pyhon的解释性,决定了解释器只关心文件内容,不关心文件后缀名)。
- 第三阶段:Python解释器解释执行刚刚加载到内存中test.py的代码( ps:在该阶段,即真正执行代码时,才会识别Python的语法,执行文件内代码,当执行到name="egon"时,会开辟内存空间存放字符串"egon")。
Python解释器与文件本编辑的异同
- 相同点:Python解释器是解释执行文件内容的,因而Python解释器具备读py文件的功能,这一点与文本编辑器一样。
- 不同点:文本编辑器将文件内容读入内存后,是为了显示或者编辑,根本不去理会Python的语法,而Python解释器将文件内容读入内存后,可不是为了给你瞅一眼Python代码写的啥,而是为了执行Python代码、会识别Python语法。
字符编码
字符编码是将人类的字符编码成计算机能识别的数字,这种转换必须遵循一套固定的标准,该标准无非是人类字符与数字的对应关系,称之为字符编码表。
- ASCII (ASCII编码表一个字节表示, 一个128个字符)
- Unicode (Unicode编码表固定大小的编码使用两个字节来表示字符,字母和汉字统一都是占用两个字节,这样浪费空间) 世界通用的标准,但是太占用空间。
- 本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8(Unicode Transformation Format-8)编码。 utf-8 (编码表,大小可变的编码字母使用1个字节,汉字使用3个字节)
- gbk (可以表示汉字,而且范围广,宇母使用1个字节,汉字2个字节)
- gb2312 (可以表示汉字,gb2312 < gbk)
- big5码(繁体中文,台湾,香港)
涉及到字符编码的两种情况
- 一个Python文件中的内容是由一堆字符组成的,存取均涉及到字符编码问题(Python文件并未执行,前两个阶段均属于该范畴)。
- Python中的数据类型字符串是由一串字符组成的(Python文件执行时,即第三个阶段)。
内存中为什么不能用utf-8
为什么内存用Unicode,而不直接使用UTF-8呢?这样不就可以直接把代码从内存直接丢入硬盘了吗?出现这个问题的原因是硬盘中还躺了其他国家的代码,各个国家的代码的二进制还需要运行在计算机上使用,因此内存中必须使用Unicode的编码,因为Unicode能和硬盘中其他国家的二进制中的代码进行转换,但是UTF-8只是简化了代码的存储,它并不能与其他国家硬盘中的代码进行关系转换。总而言之只有Unicode编码才能运行其他国家硬盘中的代码,而UTF-8的代码无法进行该操作。
内存中还使用Unicode编码,是因为历史遗留问题造成的,但是因为现在写代码使用的都是UTF-8代码,所以以后内存中的代码都将变成UTF-8代码,并且以前遗留的各个国家的代码都将被淘汰,所以未来内存中使用的编码也将使用UTF-8编码替代Unicode编码。
乱码问题
- 保证不乱码的核心法则就是,字符按照什么标准而编码的,就要按照什么标准解码,此处的标准指的就是字符编码。
- 在内存中写的所有字符,一视同仁,都是Unicode编码,比如我们打开编辑器,输入一个“你”,我们并不能说“你”就是一个汉字,此时它仅仅只是一个符号,该符号可能很多国家都在使用,根据我们使用的输入法不同这个字的样式可能也不太一样。只有在我们往硬盘保存或者基于网络传输时,才能确定”你“到底是一个汉字,还是一个日本字,这就是Unicode转换成其他编码格式的过程了。简而言之,就是内存中固定使用的就是Uncidoe编码,我们唯一能改变的就是存储到硬盘时使用的编码。
- Unicode----->encode(编码)-------->gbk
- Unicode<--------decode(解码)<----------gbk
Python2和3字符编码的区别
待补充
基本的文件操作
文件是操作系统为用户或应用程序提供的一个读写硬盘的虚拟单位。文件的操作是基于文件,即文件的操作核心就是:读和写。也就是只要我们想要操作文件就是对操作系统发起请求,然后由操作系统将用户或应用程序对文件的读写操作转换成集体的硬盘指令(比如控制盘片转动,控制机械手臂移动,以此来读取数据)。
内存无法永久保存数据,但凡我们想要永久保存数据都需要把文件保存到硬盘中,而操作文件就可以实现对硬件的操作。
打开文件分为三步:
- 打开文件
- 读写
- 关闭
#1. 从硬盘中读取数据
# 如果我们需要打开一个文件,需要向操作系统发起请求,要求操作系统打开文件,占用操作系统资源。Python中使用open()方法可以打开某个具体的文件,open()方法内写入文件路径。
open(r'/Users/mac/desktop/jupyter/pythonCourseware/32.txt')
#如果给列表增加值,我们需要给列表赋值后才能给对应的列表增加值。文件也是如此。
lis = [1,2,3]
lis.append(4)
lis.append(5)
# 打开文件
f = open(r'/Users/mac/desktop/jupyter/pythonCourseware/32.txt')
print(f)
打开文件之后,文件不仅占用了内存,他还对应了操作系统打开的以文件,相当于使用文本编辑器打开了一个文件。并且我们说了我们操控文件只是为了读和写,因此打开文件并不是目的,读和写才是目的,接下来我们尝试如何读写文件。
# read模式打开文件
f = open(r'/Users/mac/desktop/jupyter/pythonCourseware/32.txt', mode='r')
# 读取文件内容,向操作系统发起读请求,会被操作系统转成具体的硬盘操作,将内容由硬盘读入内存
data = f.read()
print(data)
# 由于Python的垃圾回收机制只回收引用计数为0的变量,但是打开文件还占用操作系统的资源,所以我们需要回收操作系统的资源资源
# del f 只是回收变量f
f.close()
# write模式打开文件
f = open(r'/Users/mac/desktop/jupyter/pythonCourseware/32.txt', mode='w')
f.write("""name = 'nick'
pwd = '123'""")
f.close()
f = open(r'/Users/mac/desktop/jupyter/pythonCourseware/32.txt', mode='r')
data = f.read()
print(data)
绝对路径和相对路径
绝对路径
- Windows系统绝对路径从盘符(C:\、D:\)开始写一个完整的路径。
- macos系统从根目录(/Users)开始写一个完整的路径。
相对路径
f = open('32.txt') # 32.txt与该.md文档同路径位置
文件的三种打开模式
文件操作的基础模式有三种(默认的操作模式为r模式):
- r模式为read
- w模式为write
- a模式为append
文件读写内容的格式有两种(默认的读写内容的模式为b模式):
- t模式为text(文本模式)
- b模式为bytes(二进制模式)
*需要注意的是:t、b这两种模式均不能单独使用,都需要与r/w/a之一连用。
文件打开模式之r模式
r: read,只读模式,只能读不能写,文件不存在时报错。
f = open('32.txt', mode='r') # 报错
f.write()
f.close()
首先,打开文件时,使用了 ‘r’ 模式,即只读模式。在只读模式下,是不允许进行写入操作的。因此,当调用 f.write() 进行写入操作时,会引发错误。
# rt: read by text
# windows的操作系统默认编码为gbk,因此需要使用utf8编码
f = open('32.txt', mode='rt', encoding='utf8')
data = f.read()
print(data)
print(f"type(data): {type(data)}")
f.close()
在触摸里奔跑
在黑夜里舞蹈
type(data): <class 'str'>
# rb: read by bytes
f = open('32.txt', mode='rb')
data = f.read()
print(data)
print(f"type(data): {type(data)}")
f.close()
b'\xe5\x9c\xa8\xe8\xa7\xa6\xe6\x91\xb8\xe9\x87\x8c\xe5\xa5\x94\xe8\xb7\x91\r\n\xe5\x9c\xa8\xe9\xbb\x91\xe5\xa4\x9c\xe9\x87\x8c\xe8\x88\x9e\xe8\xb9\x88'
type(data): <class 'bytes'>
f = open('32.txt', mode='rt', encoding='utf8')
data1 = f.read()
data2 = f.read()
print(f"data1: {data1}")
print(f"data2: {data2}")
f.close()
data1: 在触摸里奔跑
在黑夜里舞蹈
data2:
在代码中,使用 f.read() 两次读取文件内容,但是在第二次调用 f.read() 时,文件指针已经到达文件末尾,因此无法读取到任何内容。这是因为 f.read() 会一次性读取整个文件的内容到内存,并将文件指针移动到文件末尾。
如果想要重新读取文件内容,需要在第二次读取之前,先将文件指针移动到文件开头。可以使用 f.seek(0) 来将文件指针移动到文件开头,然后再进行读取操作。
f = open('32.txt', mode='rt', encoding='utf8')
data1 = f.read()
f.seek(0) # 将文件指针移动到文件开头
data2 = f.read()
print(f"data1: {data1}")
print(f"data2: {data2}")
f.close()
data1: 在触摸里奔跑
在黑夜里舞蹈
data2: 在触摸里奔跑
在黑夜里舞蹈
由于f.read()一次性读取文件的所有内容,如果文件非常大的话,可能会造成内存爆掉,即电脑卡死。因此可以使用f.readline()/f.readlines()读取文件内容。
# f.readline()/f.readlines()
f = open('32.txt', mode='rt', encoding='utf8')
print(f"f.readable(): {f.readable()}") # 判断文件是否可读
data1 = f.readline()
data2 = f.readlines()
print(f"data1: {data1}")
print(f"data2: {data2}")
f.close()
f.readable(): True
data1: 在触摸里奔跑
data2: ['在黑夜里舞蹈']
-
f.readline() 方法用于逐行读取文件内容,并返回一行的字符串。每次调用 f.readline() 都会读取文件中的下一行。在示例代码中,第一次调用 f.readline() 会读取文件的第一行,赋值给 data1。如果继续调用 f.readline(),可以读取下一行,依此类推。
-
f.readlines() 方法用于一次性读取文件的所有行,并返回一个包含所有行的列表。每一行作为一个字符串元素存储在列表中。在示例代码中,f.readlines() 会将文件的所有行读取到 data2 列表中。
-
另外,使用 f.readable() 方法可以检查文件是否可读。如果文件以只读模式打开且可读,则返回 True,否则返回 False。
文件打开模式之w模式
w: 只能写,不能读,文件存在的时候回清空文件后再写入内容;文件不存在的时候会创建文件后写入内容。
# wt
f = open('34w.txt', mode='wt', encoding='utf8')
print(f"f.readable(): {f.readable()}")
f.write('nick 真帅呀\n') # '\n'是换行符
f.write('nick 帅的我五体投地')
f.flush() # 立刻将文件内容从内存刷到硬盘
f.close()
f.readable(): False
# wb
f = open('34a.txt', mode='wb')
f.write('nick 帅的我五体投地'.encode('unicode_escape')) # 编码成bytes类型
print(
f"type('nick 帅的我五体投地'.encode('unicode_escape')): {type('nick 帅的我五体投地'.encode('unicode_escape'))}")
f.close()
type('nick 帅的我五体投地'.encode('unicode_escape')): <class 'bytes'>
文件打开模式之a模式
a: 可以追加。文件存在,则在文件的末端写入内容;文件不存在的时候会创建文件后写入内容。
# at
f = open('34a.txt', mode='at', encoding='utf8')
print(f"f.readable(): {f.readable()}")
f.write('nick 真帅呀\n') # '\n'是换行符
f.write('nick,nick, you drop, I drop.')
f.write('nick 帅的我五体投地')
f.close()
f.readable(): False
文件打开读取二进制
b模式是通用的模式,因为所有的文件在硬盘中都是以二进制的形式存储的,需要注意的是:b模式读写文件,一定不能加上encoding参数,因为二进制无法再编码。
f = open('34w.txt', 'wb')
f.write('nick 好帅啊'.encode('utf8'))
f.close()
with管理文件操作上下文
之前我们使用open()方法操作文件,但是open打开文件后我们还需要手动释放文件对操作系统的占用。f.close()
但是其实我们可以更方便的打开文件,即Python提供的上下文管理工具——with open()。
with open('32.txt', 'rt', encoding='utf8') as f:
print(f.read())
在触摸里奔跑
在黑夜里舞蹈
with open('32.txt', 'rb') as fr, \
open('35r.txt', 'wb') as fw:
f.write(f.read())
文件的高级应用
可读 可写
- r+t: 可读、可写
- w+t: 可写、可读
- a+t: 可追加、可读
# wt
with open('36w.txt', 'wt', encoding='utf-8') as fw:
print(fw.readable())
print(fw.writable())
False
True
# w+t
with open('36w.txt', 'w+t', encoding='utf-8') as fw:
print(fw.readable())
print(fw.writable())
True
True
# r+t
with open('36w.txt', 'r+t', encoding='utf-8') as fr:
print(fr.readable())
print(fr.writable())
True
True
文件内指针移动
假设我们需要在文件内容中间的某一行增加内容,如果使用基础的r/w/a模式实现是非常困难的,因此我们需要对文件内的指针进行移动。
with open('36r.txt', 'r+t', encoding='utf-8') as fr:
fr.readline()
fr.write('nick 真衰呀') # 写在文件的最后一行
#硬盘上从来没有修改一说,硬盘上只有覆盖,即新内容覆盖新内容。
#硬盘上从来没有修改一说,硬盘上只有覆盖,即新内容覆盖新内容。
#硬盘上从来没有修改一说,硬盘上只有覆盖,即新内容覆盖新内容。
- seek(offset,whence): offset代表文件指针的偏移量,偏移量的单位是字节个数
# seek()
with open('36r.txt', 'rb') as fr:
print(f"fr.seek(4, 0): {fr.seek(4, 0)}") # 0相当于文件头开始;1相当于当前文件所在位置;2相当于文件末尾
# fr.seek(0,2) # 切换到文件末尾
r.seek(offset, whence) 方法用于将文件指针移动到指定位置。其中,offset 表示偏移量,即要移动的字节数,whence 表示起始位置。具体参数说明如下:
- whence=0:从文件开头开始计算偏移量。
- whence=1:以当前位置为基准进行偏移。
- whence=2:以文件末尾为基准进行偏移。
在示例代码中,fr.seek(4, 0) 表示将文件指针移动到距离文件开头 4 个字节的位置,并返回移动后的位置。
另外,如果想将文件指针切换到文件末尾,可以使用 fr.seek(0, 2)。
- tell(): 每次统计都是从文件开头到当前指针所在位置
# tell()
with open('36r.txt', 'rb') as fr:
fr.seek(4, 0)
print(f"fr.tell(): {fr.tell()}")
fr.tell() 方法用于返回当前文件指针相对于文件起始位置的字节偏移量。
- read(n): 只有在模式下的read(n),n代表的是字符个数,除此之外,其他但凡涉及文件指针的都是字节个数
# read()
with open('36r.txt', 'rt', encoding='utf-8') as fr:
print(f"fr.read(3): {fr.read(3)}")
fr.read(size) 方法用于从文件中读取指定大小的字符或字节。其中,size 表示要读取的字符或字节的大小。
在示例代码中,fr.read(3) 表示从文件中读取前 3 个字符,并返回读取的字符串。
- truncate(n): truncate(n)是截断文件,所以文件的打开方式必须可写,但是不能用w或w+等方式打开,因为那样直接清空文件了,所以truncate()要在r+或a或a+等模式下测试效果。它的参照物永远是文件头。并且truncate()不加参数,相当于清空文件。
# truncate()
with open('36r.txt', 'ab') as fr:
fr.truncate(2) # 截断2个字节后的所有字符,如果3个字节一个字符,只能截断2/3个字符,还会遗留1/3个字符,会造成乱码
fr.truncate(size) 方法用于将文件截断到指定大小,即将文件截断为指定字节数。其中,size 表示截断后文件的大小。
在示例代码中,fr.truncate(2) 表示将文件截断为只保留前 2 个字节之后的内容。如果文件中的一个字符需要占用多个字节的情况下(如 UTF-8 编码),则只能截断到最近一个完整的字符位置,可能会导致截断后最后一个字符的部分内容丢失。
文件修改的两种方式
文件的数据是存放于硬盘上的,因而只存在覆盖、不存在修改这么一说,我们平时看到的修改文件,都是模拟出来的效果
方式一
将硬盘存放的该文件的内容全部加载到内存,在内存中是可以修改的,修改完毕后,再由内存覆盖到硬盘(word,vim,nodpad++等编辑器)。
import os
with open('37r.txt') as fr, \
open('37r_swap.txt', 'w') as fw:
data = fr.read() # 全部读入内存,如果文件很大,会很卡
data = data.replace('tank', 'tankSB') # 在内存中完成修改
fw.write(data) # 新文件一次性写入原文件内容
# 删除原文件
os.remove('37r.txt')
# 重命名新文件名为原文件名
os.rename('37r_swap.txt', '37r.txt')
print('done...')
方式二
将硬盘存放的该文件的内容一行一行地读入内存,修改完毕就写入新文件,最后用新文件覆盖源文件。
import os
with open('37r.txt') as fr,\
open('37r_swap.txt', 'w') as fw:
# 循环读取文件内容,逐行修改
for line in fr:
line = line.replace('jason', 'jasonSB')
# 新文件写入原文件修改后内容
fw.write(line)
os.remove('37r.txt')
os.rename('37r_swap.txt', '37r.txt')
print('done...')
总而言之,修改文件内容的思路为:以读的方式打开原文件,以写的方式打开一个新的文件,把原文件的内容进行修改,然后写入新文件,之后利用os模块的方法,把原文件删除,重命名新文件为原文件名,达到以假乱真的目的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现