【编码】
编码的本质就是让只认识0和1的计算机,能够理解我们人类使用的语言符号,并且将数据转换为二进制进行存储和传输。
这种从人类语言到计算机语言转换的形式,就叫做编码表,它让人类语言和计算机语言能够一一对应起来。
【二进制】
以此类推,当有八座烽火台的时候,我们就能表示2的8次方,也就是256种状态,它由8个0或1组成。
你的手机“流量”,就是这么计算的:
而百兆宽带,下载速度最多能达到十多兆,是因为运营商的带宽是以比特每秒为单位的,比如100M就是100Mbit/s。
而我们常看到的下载速度KB却是以字节每秒为单位显示的,1byte = 8bit,所以运营商说的带宽得先除以8,你的百兆宽带下载速度,也就是十几兆了。
【编码表】
具体用哪些二进制数字表示哪个符号,理论上每个人都可以有自己的一套规则(这就叫编码)。
但大家如果想要互相沟通而不造成混乱,就必须使用相同的编码规则。如果使用了不同的编码规则,那就会彼此读不懂,这就是“乱码”的由来。
一开始,是美国首先出台了ASCII编码(读音:/ˈæski/),统一规定了常用符号用哪些二进制数来表示。
因为英文字母、数字再加上其他常用符号,也就100来个,因此使用7个比特位(最多表示128位)就够用了,所以一个字节中被剩下的那个比特位就被默认为0。
再后来呢,这套编码表传入欧洲,才发现这128位不够用啊。比如说法语字母上面还有注音符,这个怎么区分?得!把最后一个比特位也编进来吧。因此欧洲普遍使用一个全字节(8个比特位)进行编码,最多可表示256位,至此,一个字节就用满了!
但是前面的状态0-127位可以共用,但从状态128到255这一段的解释就完全乱套了,比如135在法语,希伯来语,俄语编码中完全是不同的符号。
当计算机漂洋过海来到中国后,问题又来了,计算机完全不认识博大精深的中文,当然也没法显示中文;而且一个字节的256位都被占满了,但中国有10万多个汉字,256位连塞牙缝都不够啊。
于是中国科学家自力更生,重写了一张编码表,也就是GB2312,它用2个字节,也就是16个比特位,来表示绝大部分(65535个)常用汉字。后来,为了能显示更多的中文,又出台了GBK标准。
不仅中国,其他国家也都搞出自己的一套编码标准,这样的话地球村村民咋沟通?日本人发封email给中国人,两边编码表不同,显示的都是乱码。
为了沟通的便利,Unicode(万国码)应运而生,这套编码表将世界上所有的符号都纳入其中。每个符号都有一个独一无二的编码,现在Unicode可以容纳100多万个符号,所有语言都可以互通,一个网页上也可以显示多国语言。
看起来皆大欢喜。但是!问题又来了,自从英文世界吃上了Unicode这口大锅饭,为迁就一些占用字节比较多的语言,英文也要跟着占两个字节。比如要存储A,原本00010001就可以了,现在偏得用两个字节:00000000 00010001才行,这样对计算机空间存储是种极大的浪费!
基于这个痛点,科学家们又提出了天才的想法:UTF-8(8-bit Unicode Transformation Format)。它是一种针对Unicode的可变长度字符编码,它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度,而当字符在ASCII码的范围时,就用一个字节表示,所以UTF-8还可以兼容ASCII编码。
Unicode与UTF-8这种暧昧的关系一言以蔽之:Unicode是内存编码的规范,而UTF-8是如何保存和传输Unicode的手段。
2、8、16,分别是2的1次方、3次方、4次方。这一点使得三种进制之间可以非常直接地互相转换。
8进制是用0,1,2,3,4,5,6,7;16进制是用0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f来表示。
几种编码方案在当前的使用情况:
第0,计算机是有自己的工作区的,这个工作区被称为“内存”。数据在内存当中处理时,使用的格式是Unicode,统一标准。
在Python3当中,程序处理我们输入的字符串,是默认使用Unicode编码的,所以你什么语言都可以输入。
第1,数据在硬盘上存储,或者是在网络上传输时,用的是UTF-8,因为节省空间。但你不必操心如何转换UTF-8和Unicode,当我们点击保存的时候,程序已经“默默地”帮我们做好了编码工作。
第2,一些中文的文件和中文网站,还在使用GBK,和GB2312。
基于此,有时候面对不同编码的数据,我们要进行一些操作来实现转换。这里就涉及接下来要讲的【encode】(编码)和【decode】(解码)的用法。
【encode()和decode()】
编码,即将人类语言转换为计算机语言,就是【编码】encode();反之,就是【解码】decode()。
将人类语言编码后得到的结果,有一个相同之处,就是最前面都有一个字母b,比如b'\xce\xe2\xb7\xe3',这代表它是bytes(字节)类型的数据。可以用type()函数验证一下:
所谓的编码,其实本质就是把str(字符串)类型的数据,利用不同的编码表,转换成bytes(字节)类型的数据。
我们再来区分下字符和字节两个概念。
字符是人们使用的记号,一个抽象的符号,这些都是字符:'1', '中', 'a', '$', '¥' 。
字节则是计算机中存储数据的单元,一个8位的二进制数。
编码结果中除了标志性的字母b,你还会在编码结果中看到许多\x,你再观察一下这个例子:b'\xce\xe2\xb7\xe3'。
\x是分隔符,用来分隔一个字节和另一个字节。
分隔符还挺常见的,你经常会看到网址里面有好多的%,它们也是分隔符,替换了Python中的\x。比如像下面这个:
https://www.baidu.com/s?wd=%E5%90%B4%E6%9E%AB
它的意思就是在百度里面,搜索“吴枫”,使用的是UTF-8编码。你眯着眼睛看一看上面的UTF-8编码结果和这一串网址的差异,其实它们除了分隔符以外,是一模一样的。
此外,用decode()解码的时候则要注意,UTF-8编码的字节就一定要用UTF-8的规则解码,其他编码同理,否则就会出现乱码或者报错的情况。
*例:
print(b'\xe6\x88\x91\xe7\x88\xb1\xe4\xbd\xa0'.decode('utf-8')) 运行结果:我爱你
你看到大写字母K被编码后还是K,但这两个K对计算机来说意义是不同的。前者是字符串,采用系统默认的Unicode编码,占两个字节。后者则是bytes类型的数据,只占一个字节。这也验证我们前面所说的编码就是将str类型转换成bytes类型。
【文件读写】
文件读写,是Python代码调用电脑文件的主要功能,能被用于读取和写入文本记录、音频片段、Excel文档、保存邮件以及任何保存在电脑上的东西。
假如你有一项工作,需要把100个Word文档里的资料合并到1个文件上,一个个地复制粘贴多麻烦啊,这时你就能用上Python了。或者,当你要从网上下载几千条数据时,直接用Python帮你把数据一次性存入文件也是相当方便。
因此,Python能把我们从重复性工作中解放出来。
【文件读写】,是分为【读】和【写】两部分的。
【读取文件】
【第1步-开】使用open()函数打开文件。
file1这个变量是存放读取的文件数据的,以便对文件进行下一步的操作。
open()函数里面有三个参数,第一个参数是文件的保存地址,一定要写清楚,否则计算机找不到。
要找到你的文件地址,只需要把你要打开的文件直接拖到编辑器终端的窗口里,就会显示出文件地址,然后复制一下就好。
不过文件的地址有两种:相对路径和绝对路径,拖到终端获取的地址是绝对路径。这两种地址,Mac和Windows电脑还有点傲娇地不太一样,下面我就帮大家捋一捋。
绝对路径就是最完整的路径,相对路径指的就是【相对于当前文件夹】的路径,也就是你编写的这个py文件所放的文件夹路径!
如果你要打开的文件和open.py在同一个文件夹里,这时只要使用相对路径就行了,而要使用其他文件夹的文件则需使用绝对路径。
Mac电脑:
现在我的txt文件和py文件都放在桌面的test文件夹里。
我将txt文件拖入终端窗口,获得文件的绝对路径:
当我用open()函数打开的时候,就可以写成:
在这种情况下,你写绝对和相对路径都是可以的。
假如现在这个txt文件,是放在test文件夹下面一个叫做word的文件夹里,绝对路径和相对路径就变成:
Windows电脑:
常用\来表示绝对路径,/来表示相对路径。
所以当你把文件拖入终端的时候,绝对路径就变成:
但是呢,别忘了\在Python中是转义字符,所以时常会有冲突。为了避坑,Windows的绝对路径通常要稍作处理,写成以下两种格式:
获取文件的相对路径还有个小窍门,用VS Code打开文件夹,在文件点击右键,选择:
现在,把这行代码复制到你的open.py文件中,然后把文件地址替换成你自己的地址。
第二个参数表示打开文件时的模式。这里是字符串 'r',表示 read,表示我们以读的模式打开了这个文件。
你可能会疑惑,为什么打开的时候就要决定是读还是写,之后决定不行吗?这是因为,计算机非常注意数据的保密性,在打开时就要决定以什么模式打开文件。
除了'r',其他还有'w'(写入),'a'(追加)等模式。
第三个参数encoding='utf-8',表示的是返回的数据采用何种编码,一般采用utf-8或者gbk。注意这里是写encoding而不是encode噢。
【第2步-读】用read()函数进行读取的操作。
在编辑器窗口【右键】,选择【在终端中运行Python文件】,这时终端显示的是:
打印出了abc.txt文件里面的内容,它会读成字符串的数据形式。
【第3步-关】关闭文件,使用的是close()函数。
为啥要关闭文件呢?原因有两个:1.计算机能够打开的文件数量是有限制的,open()过多而不close()的话,就不能再打开文件了。2.能保证写入的内容已经在文件里被保存好了。
文件关闭之后就不能再对这个文件进行读写了。如果还需要读写这个文件的话,就要再次 open() 打开这个文件。
【总结——读文件】
【写入文件】
【第1步-开】以写入的模式打开文件。
第二个参数改成'w',表示write,即以写入的模式打开文件。
【第2步-写】往文件中写入内容,使用write()函数。
往“abc.txt”文件中写入了“张无忌”和“宋青书”这两个字符串。\n表示另起一行。
当你打开txt文件查看数据:
诶?原来文件里的周芷若和赵敏去哪里了?
是这样子的,'w'写入模式会给你暴力清空掉文件,然后再给你写入。如果你只想增加东西,而不想完全覆盖掉原文件的话,就要使用'a'模式,表示append,追加的意思。
【第3步-关】还是要记得关闭文件,使用close()函数。
*有两个小提示:1.write()函数写入文本文件的也是字符串类型。2.在'w'和'a'模式下,如果你打开的文件不存在,那么open()函数会自动帮你创建一个。
【小练习】1.请你在一个叫1.txt文件里写入字符串'难念的经' 2.然后请你读取这个1.txt文件的内容,并打印出来。
【总结——写文件】
现在问题来了,如果我们想写入的数据不是文本内容,而是音频和图片的话,该怎么做呢?
不同的读写方式:
我们可以看到里面有'wb'的模式,它的意思是以二进制的方式打开一个文件用于写入。因为图片和音频是以二进制的形式保存的,所以使用wb模式就好了,这在今天的课后作业我们会用到。
这里再顺便补充一个用法,为了避免打开文件后忘记关闭,占用资源或当不能确定关闭文件的恰当时机的时候,我们可以用到关键字with,之前的例子可以写成这样:
所以之后当你看到with open...as这种打开文件的语法格式也要淡定,这种还挺常见的。
【小练习】
最近期末快到了,霍格沃兹魔法学校准备统计一下大家的成绩。
评选的依据是什么呢?就是同学们平时的作业成绩。
现在有这样一个叫scores.txt的文件,里面有赫敏、哈利、罗恩、马尔福四个人的几次魔法作业的成绩。
但是呢,因为有些魔法作业有一定难度,教授不强制同学们必须上交,所以大家上交作业的次数并不一致。
你可以在自己的电脑里新建一个scores.txt来操作。
希望你来统计这四个学生的魔法作业的总得分,然后再写入一个txt文件。
一个非常粗糙的思路应该是:拿到txt文件里的数据,然后对数据进行统计,然后再写入txt文件。
首先,毫无疑问,肯定是打开文件。
接着就是读取文件了。一般来说,我们是用read()函数,但是在这里,我们是需要把四个人的数据分开处理的,我们想要按行处理,而不是一整个处理,所以读的时候也希望逐行读取。
因此,我们需要使用一个新函数readlines(),也就是“按行读取”。
用print()函数打印一下,看看这种方法读出来的内容是咋显示的:
readlines() 会从txt文件取得一个列表,列表中的每个字符串就是scores.txt中的每一行。而且每个字符串后面还有换行的\n符号。
这样一来,我们就可以使用for循环来遍历这个列表,然后处理列表中的数据:
现在我们要把这里每一行的名字、分数也分开,这时需要我们使用split()来把字符串分开,它会按空格把字符串里面的内容分开。
看上图第一行的罗恩 23 35 44,它将被分为['罗恩', '23', '35', '44']。
显然,对比上面两个终端的图,split()又把每一行的内容分成了一个个的字符串,于是变成了一个个列表。
split()是我们没有学过的对字符串的处理方法,在这里想插一句,对数据类型的处理是有很多种方法的,但我们不可能一次学完,而应该学习最基础必要的知识,然后在需要用到新知识时,再继续学。
split()是把字符串分割的,而还有一个join()函数,是把字符串合并的。
join()的用法是str.join(sequence),str代表在这些字符串之中,你要用什么字符串连接,在这里两个例子,一个是空字符串,一个是横杠,sequence代表数据序列,在这里是列表a。
在这里只是为了让大家理解join(),不需要记忆,之后再用再看就好。
回到哈利波特的那一步,这4个列表的第0个数据是姓名,之后的就是成绩。我们需要先统计各人的总成绩,然后把姓名和成绩放在一起。
还是可以用for...in...循环进行加法的操作:
接下来就是把成绩写入一个空的列表,因为这样才有助于我们之后写入一个txt文件。
最后写入文件:
15行的代码是打开一个叫winner.txt的文件。(如果电脑中不存在winner.txt的话,这行代码会帮你自动新建一个空白的winner.txt)
16行的代码是以writelines()的方式写进去,为什么不能用write()?因为final_scores是一个列表,而write()的参数必须是一个字符串,而writelines()可以是序列,所以我们使用writelines()。
【课后练习】——数据转移中的变化
练习目标
我们会通过今天的作业,复习课堂上学到的知识:编码和解码以及文件读写。
练习要求
今天的练习包含3个小练习。
练习1:主要是想要你自己来动手操作一下编码和解码;
练习2:尝试一下图片的读写;
练习3:完成文件转移之间的数据处理,让数据发生变化。
【练习1】
请你根据代码中的要求,一步一步完成。
【练习2】
请你通过文件读写命令,读取 photo1 里的数据(提示见代码区开头)。
然后,新建名为“photo2”的图片(在同一个文件夹),写入读到的数据。
这样,我们就通过文件读写的代码,完成了图片的复制(而非鼠标右键)。
提示1:photo1的图片是位于同一个文件夹下,所以,直接写文件名即可(注意有后缀)。
提示2:可用课堂末尾提到的with open...as这种语句来进行文件读写,注意缩进。
提示3:因为图片是以二进制的形式保存的,所以读写时需要用rb和wb模式。
【练习3】——在读写之间处理数据
在课堂上,我们已经见识过了:文件的一读一写之间,可以对数据进行一定的处理。
请你运行一次右侧的代码,重新体验一下那个过程。
现在,我们计划对课堂上得到的“winner”文档再行处理一下。
让学员的成绩从高到低排列,然后放到新文档“winner_new.txt”。
*字符串内的字符也是可以通过偏移量进行提取的(具体的提取和切片的方法和列表一致)。
如果你成功写出了代码,先运行参考代码,你会发现并没有什么变化。
因为“winner_new.txt”已经存在,且处理方式不变(相当再写入了一次排序后的成绩)。
然后,请你将代码28行的“sort(reverse=True)”括号中的“reverse=True”去掉。
再运行一下,看下“winner_new”文档里的数据发生了什么变化。