漫步人生路之Python旅途 (六) 心照神交
二次编码和解码:
encode(str:编码):参数编码方式,返回编码后的结果.
# 编码 str_1 = "编码" str_2 = str_1.encode("utf-8") # 使用utf-8进行编码 print(str_2) # 打印内容如下: b'\xe7\xbc\x96\xe7\xa0\x81' # 共6个字节 # decode(str:解码):参数编码方式,返回解码后的结果. str_1 = "编码" str_2 = str_1.encode("utf-8") # 使用utf-8进行编码 print(str_2) str_3 = str_2.decode("utf-8") # 使用utf-8进行解码 print(str_3) 打印效果如下: b'\xe7\xbc\x96\xe7\xa0\x81' # 编码后的数据 编码 # 解码后的数据
注意使用什么编码方式进行的编码,要使用什么编码方式进行解码,例如使用utf-8进行编码在解码时就要使用utf-8解码.
下面是使用utf-8编码,使用gbk解码的结果
str_1 = "编码" print(str_1) str_2 = str_1.encode("utf-8") # utf编码方式 print(str_2) str_3 = str_2.decode("gbk") # gbk解码 print(str_3) 打印效果如下: 编码 # 原始数据 b'\xe7\xbc\x96\xe7\xa0\x81' # utf-8 编码 缂栫爜 # gbk解码后的数据乱码
还有个地方需要注意的就是utf-8编码中文是3个字节.gbk的解码中文是2个字节,所以当utf-8编码的汉字如果是偶数用gbk解码只会乱码,但是不会报错,如果utf-8用奇数编码在使用gbk解码时就会报错出现问题.这是因为utf8每个中文3个字节.如果utf-8 乘以奇数个中文结果一定是个奇数,而gbk每个中文是2个字节无论乘以奇数还是偶数结果都是偶数.所以会解析不完整.如下所示:
str_1 = "编码呀" print(str_1) str_2 = str_1.encode("utf-8") # utf-8编码 print(str_2) str_3 = str_2.decode("gbk") # gbk解码 print(str_3) 打印内容如下: unicodedecodeerror: 'gbk' codec can't decode byte 0x80 in position 8: incomplete multibyte sequence
所以在使用什么编码方式进行编码时,要使用同样的编码进行解码.
一些可能会遇到的问题:
for的死循环,要规避这类错误.
list_1 = [1,2,3,4] for i in list_1: list_1.append(i)
通过for循环清空列表的陷阱1:从末尾开始删除for的陷阱
list_1 = [1,2,3,4,5] for i in list_1: list_1.pop() # 删除列表最后一个元素 print(list_1) # 打印内容如下 [1, 2] # 发现和我们想的不一样.
个人理解:用for循环列表时,每循环一次for指针+1个位置指向第二个元素,以此类推.
下面我们假设for指针+1指向第二个元素.来推断上面上面问题出现的原因.
第一次循环:
列表list_1 = [1,2,3,4,5] list_1.pop() 删除了元素5,此时i=1 for指针+1指向下一个元素
第二次循环
列表list_1 = [1,2,3,4] for指针+1指向了第二个元素2,此时i=2.
list_1.pop() 删除了元素4,for指针+2指向列表的第三个元素.
列表list_1 = [1,2,3],此时for指针+2指向了第三个元素3,此时i=3.
list_1.pop() 删除了元素3,for指针此时发现已经是列表的结尾,所以不再进行循环.
最终list_1 = [1,2]
上述例子如果不好理解,可以用for循环打印列表来进行对比
第一次循环
列表list_1 = [1,2,3,4,5]用for进行循环时,当i=1时,打印1
第二次循环
列表list_1 = [1,2,3,4,5]第二次循环i=2,打印2. for认为此时列表是[2,3,4,5]
第三次循环
列表list_1 = [1,2,3,4,5]第三次循环i=3,打印3. for认为此时列表是[3,4,5]
第四次循环
列表list_1 = [1,2,3,4,5]第四次循环i=4,打印4. for认为此时列表是[4,5]
第五次循环
列表list_1 = [1,2,3,4,5]第五次循环i=5,打印5. for认为此时列表是[5]
for结束.
通过for小循环清空列表的陷阱2:从头开始删除for的陷阱
list_1 = [1,2,3,4,5] for i in list_1: del list_1[0] print(list_1) # 打印内容如下: [4, 5]
个人解析:
第一次循环
列表list_1 = [1,2,3,4,5] i = 1 删除了元素1, 指针+1指向下一个元素
第二次循环
列表list_1 = [2,3,4,5] for指针+1后指向列表的第二个位置.而此时列表删除第一个元素后列表发生了变化.for指针+1指向了变化后的第二个元素3,所以此时i=3
但是del list_1[0] 删除了列表第一个元素2 for指针+2指向列表的第三个元素
第三次循环
列表list_1 = [3,4,5] for指针+2指向列表的第三个元素也就是元素5
但是del list_1[0]删除了列表的第一个元素也就是元素3.
此时for发现已经到了元素末尾,所以就终止了循环
而此时列表list_1 = [4,5]所以列表没有被清空.
for循环删除列表的正确方式
第一种
list_ = [1,2,3,4,5] for i in range(0,len(list_)): list_.pop() # 从末尾开始删除 # list_.pop(0) # 从头开始删除 print(list_) # 打印内容如下 []
第二种
list_1 = [1,2,3,4,5] nlen = len(list_1) for i in range(0,nlen): list_1.pop() # 从末尾开始删除元素 # list_1.pop(0) # 从头开始删除元素 print(list_1) # 打印内容如下: []
第三种
如果要求不允许使用获取长度的方式可以通过借助一个空列表缓存要被删除的列表然后进行删除的方式对列表进行删除
list_1 = [1,2,3,4,5] #list_2 = list_1[:] # 将列表list_1直接赋值给列表list_2 list_2 = list_1.copy() # 将列表list_1通过copy的方式赋值给列表list_2 for i in list_2: list_1.pop() # 从末尾开始删除 # list_1.pop(0) # 从头开始删除 # list_1.remove(i) # 通过元素删除 print(list_1) print(list_2) # 打印内容如下: [] # list_1列表被清空 [1, 2, 3, 4, 5]
第四种
list_1 = [1,2,3,4,5] for buf in list_1[::-1]: # list_1.pop() list_1.pop(0) print(list_1) # 打印内容如下: []
注意:上述将列表赋值给另一个列表用的都是浅拷贝,如果有要求或者是嵌套列表需要一层一层删除时,就需要使用深拷贝了.如下:
列表的深拷贝
from copy import deepcopy list_ = [1,2,3,4,5,[1,2,3]] list_1 = deepcopy(list_) # 通过函数实现深拷贝 print(list_1) # 打印内容如下: [1, 2, 3, 4, 5, [1, 2, 3]]
用for循环实现浅拷贝:
list_ = [1,2,3,4,5,[1,2,3]] list_1 = [i for i in list_] # for循环列表赋值的合并操作 print(list_1) # 打印内容如下: [1, 2, 3, 4, 5, [1, 2, 3]] # 上述循环拆开看如下: list_ = [1,2,3,4,5,[1,2,3]] list_1 = [] for i in list_: # for循环和列表赋值被单独拆开了 list_1.append(i) print(list_1) # 打印内容如下: [1, 2, 3, 4, 5, [1, 2, 3]]
粗谈深浅拷贝
浅拷贝:
我们在用一个变量给另一个变量赋值时,会有以下几种方式,每种方式都有它自己特殊的含义.
第一种情况:
list_1 = [1,2,3,4,5,[1,2,3]] list_2 = list_1 # 直接赋值的情况 list_1[1] = 20 # 修改list_1[1]的值 list_2[2] = 30 # 修改list_2[2]的值 print(list_1,id(list_1)) # 打印list_1和list_2.并打印它们内存地址 print(list_2,id(list_2)) 打印内容如下: ([1, 20, 30, 4, 5, [1, 2, 3]], 43927048l) ([1, 20, 30, 4, 5, [1, 2, 3]], 43927048l)
从打印内容可以看出将list_1[1]修改成20后,list_2的值也跟随着改变了.list_2[2]修改成30后list_1中的值也跟随着改变了.最终两个列表的值完全一样.再看它们的内存地址也是一样.
我们可以得出结论:当我们用list_2 = list_1这种方式赋值时,两个变量同时引用一块内存地址.无论对哪个变量进行了操作,最终访问变量时都是访问这块内存.
第二种情况:
list_1 = [1,2,3,4,5,[1,2,3]] list_2 = list_1[:] # 这种赋值方式属于浅拷贝 list_1[1] = 30 list_2[5][0] = 10 print(list_1,id(list_1[1]),id(list_1[0]),id(list_1[5][0])) print(list_2,id(list_2[1]),id(list_2[0]),id(list_2[5][0])) # 打印内容如下: ([1, 30, 3, 4, 5, [10, 2, 3]], 34896688l, 34897384l, 34897168l) ([1, 2, 3, 4, 5, [10, 2, 3]], 34897360l, 34897384l, 34897168l)
从打印信息可以看出,当对list_1[1]进行修改,list_2的值并没有随着list_1的改变而改变,从内存地址可以看出两个元素的内存地址不一样.但是打印list_1[0]和list_2[0]的地址时,由于这两个元素没有发生改变,所以同时指向一个内存地址,由此我们可以得出结论1.浅拷贝后两个列表的元素如果没发生改变,同时指向一块内存.当两个列表中有元素发生改变后,发生改变列表的元素将被引用到新开辟的内存空间.不会影响到另一个列表.但是当我们修改列表中嵌套的列表时,会发现两个列表中嵌套列表的数据都发生了改变.而且内存地址一样.由此我们得出结论2.在列表中嵌套的列表经过浅拷贝列表后.两个列表内的嵌套列表共同引用一块内存地址.无论哪个列表内的嵌套列表发生改变.都会影响另一个列表.
总结:浅拷贝最外层的元素发生改变后是开辟了一个新的内存空间并引用新的内存空间.所以不会对另一个列表有影响.但如果浅拷贝的是一个嵌套(如列表,字典可变的嵌套).那么嵌套在内部的内存地址时两个变量共同引用的.所以无论哪个内部嵌套的元素发生了变化,都会影响另一个.
第三种情况:
list_1 = [1,2,3,4,5,[1,2,3,4]] list_2 = [] list_2 = list_1.copy() # 使用copy()函数与第二种情况一样 list_1[1] = 30 list_2[5][0] = 10 print(list_1,id(list_1[1]),id(list_1[0]),id(list_1[5][0])) print(list_2,id(list_2[1]),id(list_2[0]),id(list_2[5][0])) # 打印内容如下: [1, 30, 3, 4, 5, [10, 2, 3, 4]] 8791386994416 8791386993488 8791386993776 [1, 2, 3, 4, 5, [10, 2, 3, 4]] 8791386993520 8791386993488 8791386993776
深拷贝:
深拷贝的赋值方式1:for循环
list_1 = [1,2,3,4,5,[1,2,3,4]] list_2 = [] count = 0 for i in list_1: # for循环和赋值的方式 if type(i) == list: list_2.append([]) for j in i: list_2[count].append(j) continue list_2.append(i) count += 1 print(list_2) # 打印内容如下: [1, 2, 3, 4, 5, [1, 2, 3, 4]]
深拷贝的赋值方式2:copy.deepcopy(object)
import copy list_1 = [1,2,3,4,5,[1,2,3,4]] list_2 = copy.deepcopy(list_1) print(list_2) # 打印内容如下: [1, 2, 3, 4, 5, [1, 2, 3, 4]]
粗谈深拷贝:
第一种:
import copy list_1 = [1,2,3,4] list_2 = copy.deepcopy(list_1) # list_1和list_2深拷贝 for i in list_1: print(id(i),end=" ") # 打印list1中所有元素的内存地址 print("\n") for i in list_2: print(id(i), end=" ") # 打印list2中所有元素的内存地址 # 修改list_1[0]元素和list_2[1]元素的值 list_1[0] = 10 list_2[1] = 20 print("\nlist_1:",list_1,"\nlist_2:",list_2) for i in list_1: print(id(i),end=" ") # 打印修改后的list_1所有元素内存地址 print("\n") for i in list_2: print(id(i), end=" ") # 打印修改后的list_2所有元素内存地址 # 打印内容如下: # list_1的原数据 8791390401360 8791390401392 8791390401424 8791390401456 # list_2的原数据 8791390401360 8791390401392 8791390401424 8791390401456 # 通过打印list_1和list_2所有元素内存地址发现它们的内存地址一样 # 修改list_1的数据 list_1: [10, 2, 3, 4] # 修改list_2的数据 list_2: [1, 20, 3, 4] # 打印修改的list_1的数据 8791390401648 8791390401392 8791390401424 8791390401456 # 打印修改后的list_2的数据 8791390401360 8791390401968 8791390401424 8791390401456
修改list_1和list_2的值后,可以发现list_1和list_2的对应的元素的值被修改,而且被修改元素的内存地址发生了改变.没被修改的元素内存地址还是一样.
总结1:深拷贝的列表(单层列表,不是嵌套列表),如果值没被修改,它们引用同一块内存空间,当某个元素的值发生改变后,又开辟了一个空间并引用新的内存空间.
第二种
import copy list_1 = [1,2,3,4,[1,2,3]] list_2 = copy.deepcopy(list_1) for i in list_1: print(id(i),end=" ") # 打印list_1所有元素的内存地址 print("\n") for i in list_2: print(id(i), end=" ") # 打印list_2所有元素的内存地址 打印内容如下: 8791390401360 8791390401392 8791390401424 8791390401456 37638728 8791390401360 8791390401392 8791390401424 8791390401456 37779272
总结2:深拷贝后只有内部嵌套的列表的内存地址是不一样的,所以当更改列表嵌套内的列表不会对另一个列表产生影响,因为他们不在同一块内存地址上,而最外层的元素与浅拷贝类似,当元素的值没有发生改变时引用同一块内存地址,如果发生改变将开辟一个内存空间并引用该内存空间.
python中is和==的区别
is:是判断两边的内存地址是否一样
test_1 = "abc" test_2 = 123 test_3 = "abc" print(test_1 is test_2) print(test_1 is test_3) # 打印内容如下: false true
== :是判断两边的值是否相等
test_1 = "abc" test_2 = 123 test_3 = "abc" print(test_1 == test_2) print(test_1 == test_3) # 打印内容如下: false true
总结:如果python内存地址相同那么它们的值一定相同,但如果它们的值相同它们不一定在同一内存地址.
上一篇: 初识并发编程