八、文件操作
八、文件操作
8.1、编码
8.1.1、编码方式
ASCII
表
众所周知,计算机起源于美国,英文只有26个字符,算上其他所有特殊符号也不会超过128个。字节是计算机的基本储存单位,一个字节(bytes)包括八个比特位(bit),能够表示出256个二进制数字,所以美国人在这里只是用到了一个字节的前七位即127个数字来对应了127个具体字符,而这张对应表就是ASCII码字符编码表,简称ASCII表。后来为了能够让计算机识别拉丁文,就将一个字节的最高位也应用了,这样就多扩展出128个二进制数字来对应新的符号。这张对应表因为是在ASCII表的基础上扩展的最高位,因此称为扩展ASCII表。到此位置,一个字节能表示的256个二进制数字都有了特殊的符号对应。
GBK
编码
但是,当计算机发展到东亚国家后,问题又出现了,像中文,韩文,日文等符号也需要在计算机上显示。可是一个字节已经被西方国家占满了。于是,我中华民族自己重写一张对应表,直接生猛地将扩展的第八位对应拉丁文全部删掉,规定一个小于127的字符的意义与原来相同,即支持ASCII码表,但两个大于127的字符连在一起时,就表示一个汉字,这样就可以将几千个汉字对应一个个二进制数了。而这种编码方式就是GB2312,也称为中文扩展ASCII码表。再后来,我们为了对应更多的汉字规定只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。这样能多出几万个二进制数字,就算甲骨文也能够用了。而这次扩展的编码方式称为GBK标准。当然,GBK标准下,一个像”苑”这样的中文符号,必须占两个字节才能存储显示。
Unicod
e与utf8
编码
与此同时,其它国家也都开发出一套编码方式,即本国文字符号和二进制数字的对应表。而国家彼此间的编码方式是互不支持的,这会导致很多问题。于是ISO国际化标准组织为了统一编码,统计了世界上所有国家的字符,开发出了一张万国码字符表,用两个字节即六万多个二进制数字来对应。这就是Unicode编码方式。这样,每个国家都使用这套编码方式就再也不会有计算机的编码问题了。Unicode的编码特点是对于任意一个字符,都需要两个字节来存储。这对于美国人而言无异于吃上了世界的大锅饭,也就是说,如果用ASCII码表,明明一个字节就可以存储的字符现在为了兼容其他语言而需要两个字节了,比如字母I,本可以用01001001来存储,现在要用Unicode只能是00000000 01001001存储,而这将导致大量的空间被浪费掉。基于此,美国人创建了utf8编码,而utf8编码是一种针对Unicode的可变长字符编码方式,根据具体不同的字符计算出需要的字节,对于ASCII码范围的字符,就用一个字节,而且符号与数字的对应也是一致的,所以说utf8是兼容ASCII码表的。但是对于中文,一般是用三个字节存储的。
8.1.1、编码和解码
# 字符串的编码方式方法:字符串转成二进制的数字的过程 编码
s = "浩哥" b1 = s.encode() b2 = s.encode("GBK") print(b1) # 默认utf8 : b'\xe6\xb5\xa9\xe5\x93\xa5' print(b2) # b'\xba\xc6\xb8\xe7' # 解码方法:将字节数据转成字符串的过程 print(b1.decode()) # 这里如果用GBK解码就会出现乱码 print(b2.decode("GBK")) print(type(b1)) # <class 'bytes'> print(type(b2)) # <class 'bytes'>
Python 3 最重要的新特性大概要算是对文本和二进制数据作了更为清晰的区分,不再会对bytes字节串进行自动解码。文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示。Python 3不会以任意隐式的方式混用str和bytes,正是这使得两者的区分特别清晰。
8.1、打开文件
在 Python中,如果想要操作文件,首先需要创建或者打开指定的文件,并创建一个文件对象,而这些工作可以通过内置的 open() 函数实现。open() 函数用于创建或打开指定文件,该函数的常用语法格式如下:
# 打开文件
open(文件路径, 打开模式)
file = open(file_name [, mode='r' [ , buffering=-1 [ , encoding = None ]]])
此格式中,用 [] 括起来的部分为可选参数,即可以使用也可以省略。其中,各个参数所代表的含义如下:
''' - file:表示要创建的文件对象。 - file_name:要创建或打开文件的文件名称,该名称要用引号(单引号或双引号都可以)括起来。需要注意的是,如果要打开的文件和当前执行的代码文件位于同一目录,则直接写文件名即可;否则,此参数需要指定打开文件所在的完整路径。 - mode:可选参数,用于指定文件的打开模式。可选的打开模式如表 1 所示。如果不写,则默认以只读(r)模式打开文件。 - buffering:可选参数,用于指定对文件做读写操作时,是否使用缓冲区。 - encoding:手动设定打开文件时所使用的编码格式,不同平台的 ecoding 参数值也不同,以 Windows 为例,其默认为 cp936(实际上就是 GBK 编码)。 '''
open() 函数支持的文件打开模式如表 1 所示。
模式 | 意义 | 注意事项 |
---|---|---|
r | 只读模式打开文件,读文件内容的指针会放在文件的开头。 | 操作的文件必须存在。 |
rb |
以二进制格式、采用只读模式打开文件,读文件内容的指针位于文件的开头,一般用于非文本文件,如图片文件、音频文件等。 | |
r+ | 打开文件后,既可以从头读取文件内容,也可以从开头向文件中写入新的内容,写入的新内容会覆盖文件中等长度的原有内容。 | |
rb+ |
以二进制格式、采用读写模式打开文件,读写文件的指针会放在文件的开头,通常针对非文本文件(如音频文件)。 | |
w | 以只写模式打开文件,若该文件存在,打开时会清空文件中原有的内容。 | 若文件存在,会清空其原有内容(覆盖文件);反之,则创建新文件。 |
wb |
以二进制格式、只写模式打开文件,一般用于非文本文件(如音频文件) | |
w+ | 打开文件后,会对原有内容进行清空,并对该文件有读写权限。 | |
wb+ |
以二进制格式、读写模式打开文件,一般用于非文本文件 | |
a | 以追加模式打开一个文件,对文件只有写入权限,如果文件已经存在,文件指针将放在文件的末尾(即新写入内容会位于已有内容之后);反之,则会创建新文件。 | |
ab | 以二进制格式打开文件,并采用追加模式,对文件只有写权限。如果该文件已存在,文件指针位于文件末尾(新写入文件会位于已有内容之后);反之,则创建新文件。 | |
a+ | 以读写模式打开文件;如果文件存在,文件指针放在文件的末尾(新写入文件会位于已有内容之后);反之,则创建新文件。 | |
ab+ | 以二进制模式打开文件,并采用追加模式,对文件具有读写权限,如果文件存在,则文件指针位于文件的末尾(新写入文件会位于已有内容之后);反之,则创建新文件。 |
将以上几个容易混淆的文件打开模式的功能做了很好的对比:
open()返回的文件对象常用的属性:
''' - file.name:返回文件的名称; - file.mode:返回打开文件时,采用的文件打开模式; - file.encoding:返回打开文件时使用的编码格式; - file.closed:判断文件是否己经关闭。 '''
注意,当操作文件结束后,必须调用 close() 函数手动将打开的文件进行关闭,这样可以避免程序发生不必要的错误。
8.2、读文件
Python提供了如下 3 种函数,它们都可以帮我们实现读取文件中数据的操作:
read()
函数:逐个字节或者字符读取文件中的内容;readline()
函数:逐行读取文件中的内容;readlines()
函数:一次性读取文件中多行内容。
# (1) 读字符 f = open("满江红",encoding="utf8") print(f.read()) # 默认读取所有字符 print(f.read(3)) print(f.readline()) print(f.readlines()) # (2) 读字节 f = open("满江红",mode="rb") print(f.read()) print(f.read(2)) print(f.read(3)) print(f.read(2).decode()) print(f.read(12).decode()) # (3) 循环读文件 f = open("满江红",encoding="utf8") for line in f.readlines(): print(line,end="") for line in f: print(line,end="")
8.3、写文件
f = open("满江红new", mode="w", encoding="utf8") # w:覆盖模式 a:追加模式 f.write("怒发冲冠,凭栏处、潇潇雨歇。\n") f.writelines(["抬望眼,", "仰天长啸,壮怀激烈"]) # 将字符串列表写入文件中 f.flush() import time time.sleep(100) f.close() # 没有close,只有在程序退出是才会被释放掉
8.4、seek与tell方法
文件指针用于标明文件读写的起始位置。使用 open() 函数打开文件并读取文件中的内容时,总是会从文件的第一个字符(字节)开始读起,而借助seek函数则可以移动文件指针的位置,在通过 read() 和 write() 函数读写指定位置的数据。而 tell() 函数则是获取光标当前位置。
当向文件中写入数据时,如果不是文件的尾部,写入位置的原有数据不会自行向后移动,新写入的数据会将文件中处于该位置的数据直接覆盖掉。
seek() 函数用于将文件指针移动至指定位置,该函数的语法格式如下:
file.seek(offset[, whence])
其中,各个参数的含义如下:
- whence:作为可选参数,用于指定文件指针要放置的位置,该参数的参数值有 3 个选择:0 代表文件头(默认值)、1 代表当前位置、2 代表文件尾。
- offset:表示相对于 whence 位置文件指针的偏移量,正数表示向后偏移,负数表示向前偏移。例如,当
whence == 0 &&offset == 3
(即 seek(3,0) ),表示文件指针移动至距离文件开头处 3 个字符的位置;当whence == 1 &&offset == 5
(即 seek(5,1) ),表示文件指针向后移动,移动至距离当前位置 5 个字符处。
# hi friends,welcome to oldboy! f = open("hi", mode="rb") print(f.tell()) # 0 print(f.read(3)) # b'hi ' print(f.tell()) # 3 f.seek(3, 1) print(f.read(1)) # b'e'
注意,当 offset 值非 0 时,Python 要求文件必须要以二进制格式打开,否则会抛出 io.UnsupportedOperation
错误。
8.5、章节练习
基于函数实现持久化存储的学生成绩管理系统
# 确定数据以什么数据类型和格式进行存储 import json # 全局变量 students_dict students_dict = {} def init(): global students_dict # 打开student_scores.json文件,读取json数据 try: with open("student_scores.json", "r") as f: student_scores_json = f.read() # 反序列化 students_dict = json.loads(student_scores_json) # print("初始化students_dict", students_dict) except FileNotFoundError: pass def save(): # 生成一个students_scores.json file = open("student_scores.json", "w") students_json = json.dumps(students_dict) file.write(students_json) file.close() def show_students(): ''' 查看所有学生信息 ''' # print("students_dict", students_dict) print("*" * 60) for sid, stu_dic in students_dict.items(): # print(sid,stu_dic) name = stu_dic.get("name") chinese = stu_dic.get("scores").get("chinese") math = stu_dic.get("scores").get("math") english = stu_dic.get("scores").get("english") print("学号:%4s 姓名:%4s 语文成绩:%4s 数学成绩%4s 英文成绩:%4s" % (sid, name, chinese, math, english)) print("*" * 60) def add_student(): ''' 添加一个学生和对应成绩 ''' while 1: sid = input("请输入学生学号>>>") # 判断该学号是否存在 if sid in students_dict: # 该学号已经存在! print("该学号已经存在!") else: # # 该学号不存在! break name = input("请输入学生姓名>>>") chinese_score = input("请输入学生语文成绩>>>") math_score = input("请输入学生数学成绩>>>") english_score = input("请输入学生英语成绩>>>") # 构建学生字典 scores_dict = { "chinese": chinese_score, "math": math_score, "english": english_score, } stu_dic = { "name": name, "scores": scores_dict } # print("stu_dic", stu_dic) students_dict[sid] = stu_dic # print("students_dict", students_dict) def update_student(): ''' 更新一个学生成绩 ''' while 1: sid = input("请输入学生学号>>>") # 判断该学号是否存在 if sid in students_dict: # 该学号已经存在! break else: # # 该学号不存在! print("该修改学号不存在!") chinese_score = input("请输入学生语文成绩>>>") math_score = input("请输入学生数学成绩>>>") english_score = input("请输入学生英语成绩>>>") # 修改学生成绩 scores_dict = { "chinese": chinese_score, "math": math_score, "english": english_score, } students_dict.get(sid).update({"scores": scores_dict}) print("修改成功") print("students_dict", students_dict) def delete_student(): ''' 删除一个学生和对应成绩 ''' while 1: sid = input("请输入学生学号>>>") # 判断该学号是否存在 if sid in students_dict: # 该学号已经存在! break else: # # 该学号不存在! print("该修改学号不存在!") students_dict.pop(sid) print("删除成功") print("students_dict", students_dict) def main(): # 初始化读取student_scores文件获取学生成绩字典students_dict init() while 1: print(''' 1. 查看所有学生成绩 2. 添加一个学生成绩 3. 修改一个学生成绩 4. 删除一个学生成绩 5. 保存 6. 退出程序 ''') choice = input("请输入您的选择:") if choice == "1": show_students() elif choice == "2": add_student() elif choice == "3": update_student() elif choice == "4": delete_student() elif choice == "5": save() elif choice == "6": break else: print("输入有误!") # 一般程序中的main函数是主要逻辑函数 main()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?