[Python学习日记-30] Python中数据类型与文件操作的补充(Bytes 类型、字符编码的转换、深浅 Copy)

[Python学习日记-30] Python中数据类型与文件操作的补充

Bytes 类型

字符编码的转换

深浅 Copy

Bytes 类型

一、定义

        Bytes 类型是指一堆字节的集合,在 Python 中以 b 开头的字符串都是 Bytes 类型。从前面学习的字符编码当中知道,数据存到硬盘当中都是只能存储二进制的,而且数据往硬盘上存就要以相应的字符编码(ASSCII、GBK、UTF-8)来转成二进制后在存储

文字 ——> utf-8/gbk ——> 二进制
图片 ——> jpg/png ——> 二进制
音乐 ——> mp3/wav ——> 二进制
视频 ——> mp4/mov ——> 二进制

        Bytes类型是以十六进制的形式表示两个十六进制数构成一个 Byte,并且以 b'' 来标识的字节串,如下所示

b'\xe4\xb8\x96\xe7\x95\x8c\xe5\x92\x8c\xe5\xb9\xb3'

二、作用

        计算机只能存储和读取二进制, 所以我们的字符、图片、视频、音乐等想存到硬盘上,也必须以正确的方式编码成二进制后再进行存储。

  • 对于文字,我们可以以 GBK 编码,也可以以 UTF-8、ASCII 编码
  • 对于图片,必须编码成 PNG、JPEG 等格式
  • 对于音乐,必须编码成 MP3、WAV 等

        在 Python 中,数据转成二进制后不是直接以0101010的形式来表示的,而是用字节(bytes)类型来表示,当然对于人类来说不可读。在 Python 中可以使用 encode() 方法来进行转换,字符串转成 Bytes 类型如下所示

        在 Python 中,字符串必须编码成 Bytes 类型后才能存到硬盘上。回想一下,之前学的文件操作时也没有把字符串进行编码后再存储,但是也是可以正常进行存储的,那是因为 Python 默默就干了这个事,在 Python3 中文件存储的默认编码是 UTF-8,当然我们也可以自行改变文件的默认编码,这个需要在打开文件的时候对 encoding 参数进行设定,如下代码所示

f = open(file="encode_test.txt",mode="w",encoding="gbk")    # 这样写入的数据就是按 GBK 编码的了
f.write("世界和平")
f.close()

代码输出如下:

        在 PyCharm 中打开文件默认是使用 UTF-8 编码,当我们使用 UTF-8 编码的打开以 GBK 编码写入的文件时就会发现除了最后一行写入的英文字符外其他都是乱码,这种现象在字符编码时已经讲过了,如果还不明白可以再去翻看一下。当我们修改 PyCharm 把打开 encode_test.txt 的编码改为 GBK 时会发现一切又恢复了正常,如下图所示

三、以二进制模式操作文件

        有的同学就有疑问了,那我能不能不不用 open 这个对象来帮你自动编码呢?当然也可以直接往文件里存入 Bytes 数据,我们称之为二进制模式,这个模式分别有:wb(二进制创建)、rb(二进制读)、ab(二进制追加)

1、二进制创建

f = open('bytes_test.txt','wb')
f.write(b'\xca\xc0\xbd\xe7\xba\xcd\xc6\xbd\n')    # 直接写入已经编码好的二进制
f.write('世界和平\n'.encode('gbk'))    # 使用 encode() 对字符串进行编码,编码成 GBK
f.close()

代码输出如下:

2、二进制读

        二进制读在 read() 的时候并不会进行自动解码,而是会直接读取出以十六进制形式表现的字节串,这个时候需要使用 decode() 方法来进行解码后才能显示为人类看得懂的字符,后面会说到

f = open('bytes_test.txt','rb')
print(f.read())
f.seek(0)
print(f.read().decode('gbk'))
f.close()

代码输出如下: 

        在使用 decode() 的过程中很容易遇到以下报错:

UnicodeDecodeError: 'gbk' codec can't decode byte 0x88 in position 8: incomplete multibyte sequence
Unicode解码错误:“gbk”编解码器无法解码位置8的字节0x88:不完整的多字节序列

3、二进制追加

f = open('bytes_test.txt','ab')
f.write(b'\xca\xc0\xbd\xe7\xba\xcd\xc6\xbd3\n')
f.write('世界和平4\n'.encode('gbk'))
f.close()

代码输出如下:  

 

注意:以什么编码格式编写的文件必须以该编码格式来解码,例如在 Windows 就是 GBK,Mac 就是 UTF-8,不然你会看到全是乱码!

字符编码的转换

        编码转换是指将一种编码转成另外一种编码,例如 UTF-8 转换为 GBK 。为何需要编码转换呢?这是因为不同操作系统使用的编码不同,例如 Windows 上使用的是 GBK 编码,如果想要在 Windows 上查看则需要转成 GBK 才能正常显示,不然都是乱码没法看,反过来如果你的 GBK 编码的字符相对应在 Linux\Mac 上想正常显示,也就得转成 UTF-8 编码。

一、编码与解码

         编码与解码的代码如下

s = "世界和平"
print(s.encode("utf-8"))    # 编码
s_utf8 = s.encode("utf-8")
print(s_utf8.decode("utf-8"))    # 解码

 代码输出如下:

二、编码的转换 

        在 Python3 里,内存里的字符串是以 Unicode(万国码)编码的,Unicode 的其中一个特性就是跟所有语言编码都有映射关系。所以 UTF-8 格式的文件,在 Windows 电脑上若是不能看,就可以把 UTF-8 先解码成 Unicode,再由 Unicode 编码成 GBK 就可以了,如下图所示

注意:不管在 Windows、Mac、Linux 上,你的 Pycharm IDE 都可以支持各种文件编码,所以即使是 UTF-8 的文件,在 Windows 下的 PyCharm 里也可以正常显示

深浅 Copy

        这里的 Copy 其实就是字面的意思——复制,有的同学会发现,例如字符串的复制不是直接在被复制变量和被赋值变量之间加等号就可以了吗?这里忽略了一点,其实之前我们之前提过变量生成过程的时候就说过,像整数、浮点数和字符串这类不可修改的数据变量在改变其赋值时是直接重新生成一个,内存地址是会发生改变的,而列表、集合和字典这类可变的数据类型他是内存地址不变,即如果里面的变量修改变化了,变量名指向的内存地址也不会变化。我们可以用下面的代码进行验证

dict1 = {
    "name":"Jove",
    "age":18,
    "scores":{
        "语文":130,
        "数学":60,
        "英语":98,
    }
}

dict2 = dict1
print(id(dict1),id(dict2))
dict1["age"] = 20
print(dict2)
print(id(dict1),id(dict2))

代码输出如下: 

        从代码的执行结果可以看出 dict2 跟随 dict1 的修改而改变了,这是因为 dict2=dict1 相当于只是拿到了 dict1 的内存地址,但 dict1 里的每个 key 和 value 都是有单独的内存地址的,dict1 和 dict2 会一直共享这个 dict 里的数据,不会出现像字符串 a=1 b=8 a=2,最后 b 依然等于1的情况,如下图所示

        但是有的时候还是需要复制一份完全的 dict 数据呢?为了解决这个问题列表、集合和字典当中就存在了 copy() 和 deepcopy() 方法,而像元组这类不可变的变量就没有这类方法了。

一、浅 Copy

        上面提到的现象会不会有的同学会搞懵呢?我们再用通俗点的来描述一下这个现象,首先我们把字典这一个变量比作中国这个地方,那 dict1 就是一个名称,中文叫中国,英文叫 China,法文叫 La Chine 等等,只要别人叫出这个名称大家都知道是指这一个地方,而字典里面的变量就相当于中国里面的省,而第二层当中的就相当于市,如此类推。有一天中央下发了命令,需要彻底融合大湾区,需要把佛山、广州、中山、珠海、东莞等大湾区城市融合为一个广州湾区,这就相当于上面代码中 dict1["age"] = 20,那么这会不会影响别人叫中国、China、La Chine 是所指的地方呢?很明显,不会。其实这些可变变量也可以称为容器性质的数据类型,列表、集合、字典这些可变变量都有容器性质。

        而在想要复制他们时就需要使用到 copy() 方法,我们先来使用浅 Copy 来试下效果如何,代码如下

dict1 = {
    "name":"Jove",
    "age":18,
    "scores":{
        "语文":130,
        "数学":60,
        "英语":98,
    }
}

dict2 = dict1.copy()
dict1["age"] = 20
print("dict2:",dict2)
print("dict1:",dict1)

代码输出如下:

        这样就相当于是两份独立数据了,但是为什么这个语法叫做浅 Copy 呢?试一下改 scores 里的值就知道了,代码如下

dict1 = {
    "name":"Jove",
    "age":18,
    "scores":{
        "语文":130,
        "数学":60,
        "英语":98,
    }
}

dict2 = dict1.copy()
dict1["age"] = 20
dict1["scores"]["数学"] = 77
print("dict2:",dict2)
print("dict1:",dict1)

代码输出如下:

        看到这个输出很神奇吧,dict1 和 dict2 里的 age 的值是独立的,而 scores 里的字典的分数值却是共享的。这是因为浅 Copy 会仅复制 dict 的第一层数据,更深层的 scores 下面的值依然是共享一份,如下图所示

        这里要看清楚,图中的 dict1 和 dict2 中的 name 没有改变过所以内存地址是一样,在没改前这两个 name 都确实指向同一个内存地址,但只要改任何一个的值,内存地址都会变更,就像 age 一样。 而要克服这一困难就需要深 Copy 出场了。

二、深 Copy

        若想要彻底使上面的 dict1 和 dict2 完全独立(无论有多少层数据)。那就要用 Python 中的工具包里的一个工具了——copy,直接看代码

import copy

dict1 = {
    "name":"Jove",
    "age":18,
    "scores":{
        "语文":130,
        "数学":60,
        "英语":98,
    }
}

dict2 = copy.deepcopy(dict1)
dict3 = copy.copy(dict1)    # copy 工具包的 copy 和数据类型中的 copy 是一样的
dict1["age"] = 20
dict1["scores"]["数学"] = 77

print("dict1:",dict1)
print("dict2:",dict2)
print("dict3:",dict3)

代码输出如下:

        可以看得出 dict2 已经是完全将 dict1 完全复制了,并且修改 dict1 并不会影响到 dict2,但是用深 Copy 时需要注意存储空间,例如你的字典有 500Mb 这么大,如果使用深 Copy 的话就会需要 1Gb 这样会瞬间扩大一倍的存储空间,如果存储空间不足将会导致复制失败。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JoveZou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值