Python二次编码、小数据池之心照神交

二次编码、解码、小数据池:

encode(str:编码):参数编码方式,返回字节码。

str_1 = "编码"
str_2 = str_1.encode("utf-8") # 使用utf-8进行编码
print(str_2)

# 打印内容如下
b'xe7xbcx96xe7xa0x81'

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'xe7xbcx96xe7xa0x81'
解码后的数据: 编码

注意使用什么编码方式进行的编码,要使用什么编码方式进行解码,例如使用utf-8进行编码在解码时就要使用utf-8解码。

下面是使用utf-8编码,使用gbk解码的结。

str_1 = "编码"

str_2 = str_1.encode("utf-8") # utf编码方式
print("utf-8编码:",str_2)

str_3 = str_2.decode("gbk") # gbk解码
print("gbk解码:",str_3)

# 打印内容如下
utf-8编码: b'xe7xbcx96xe7xa0x81'
gbk解码: 缂栫爜

还有个地方需要注意的就是utf-8编码中文是3个字节,gbk的解码中文是2个字节,所以当utf-8编码的汉字如果是偶数用gbk解码只会乱码,但是不会报错。如果utf-8用奇数编码在使用gbk解码时就会报错,这是因为utf8每个中文3个字节乘以奇数个中文结果一定是个奇数,而gbk每个中文是2个字节无论乘以奇数还是偶数结果都是偶数,所以会解析不完整。

如下所示:

str_1 = "编码呀"

str_2 = str_1.encode("utf-8") # utf-8编码

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循环是按照迭代器的方式进行循环的,那么列表list_1 = [1,2,3,4,5]需要循环或者说迭代5次才可以循环完整个列表。而迭代器具有只能想下执行是不能回退的特性,所以有了如下情况:

第一次循环

list_1 = [1,2,3,4,5]第一次循环迭代时,迭代器指向list_1的第一个元素1,执行list_1.pop()删除末尾元素5

第二次循环

list_1 = [1,2,3,4]第二次循环迭代时,迭代器指向list_1的第二个元素2,执行list_1.pop()删除末尾元素4

第三次循环

list_1 = [1,2,3]第三次循环迭代时,迭代器指向list_1的第三个元素3,执行list_1.pop()删除末尾元素3

第四次循环

list_1 = [1,2]第四次循环迭代时,迭代器期待指向list_1的第四个元素,而此时list_1只剩2个元素。所以迭代器会报一个StopIteration的异常,这个异常对于for来说就是循环结束的标志。于是乎for就停止了循环。最终list_1 = [1,2]

通过for循环清空列表的陷阱2:从头开始删除for的陷阱

list_1 = [1,2,3,4,5]

for i in list_1:
    list_1.pop(0)  # 从头开始清空列表

print(list_1)

# 打印内容如下
[4, 5]

与上面从末尾删除元素的类似。下面进行简单的分析:

第一次循环

list_1 = [1,2,3,4,5]  第一次循环迭代时,迭代器指向list_1的第一个元素1,执行list_1.pop(0)删除第一个元素1

第二次循环

list_1 = [2,3,4,5]  第二次循环迭代时,迭代器指向list_1的第二个元素3,执行list_1.pop(0)删除第一个元素2

第三次循环

list_1 = [3,4,5]   第三次循环迭代时,迭代器指向list_1的第三个元素5,执行list_1.pop(0)删除第一个元素3

第四次循环

list_1 = [4,5] 第四次循环迭代时,迭代器期待指向list_1的第四个元素,而此时list_1只剩2个元素。所以迭代器会报一个StopIteration的异常,这个异常对于for来说就是循环结束的标志。于是乎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)

# 打印内容如下
[]

第四种

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]]

粗谈、深浅拷贝

浅拷贝:

我们在用一个变量给另一个变量赋值时,会有以下几种方式,每种方式都有它自己特殊的含义.

第一种情况:

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)) 
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深拷贝

# 修改list_1[0]元素和list_2[1]元素的值
list_1[0] = 10
list_2[1] = 20

print("
list_1:",list_1,"
list_2:",list_2)
print("list_1元素内存地址:",end="")

for i in list_1:  # 打印list_1所有元素内存地址
    print(id(i), end=" ")

print("
list_2元素内存地址:",end="")

for i in list_2:   # 打印list_2所有元素内存地址
    print(id(i), end=" ")

# 打印内容如下
list_1: [10, 2, 3, 4] 
list_2: [1, 20, 3, 4]
list_1元素内存地址:8791365694576 8791365694320 8791365694352 8791365694384 
list_2元素内存地址:8791365694288 8791365694896 8791365694352 8791365694384

通过打印结果可以发现修改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("
")

for i in list_2:
    print(id(i), end=" ") # 打印list_2所有元素的内存地址

# 打印内容如下
8791369691984 8791369692016 8791369692048 8791369692080 36131208 
8791369691984 8791369692016 8791369692048 8791369692080 36267592

通过打印结果可以看出两个列表的最外层元素的内存地址是一样的这个我们在第一个示例中已经知道了。但是可以发现最后一个元素也就是嵌套列表的元素内存地址时不一样的。由此可以得出如下总结:

总结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内存地址相同那么它们的值一定相同,但如果它们的值相同它们不一定在同一内存地址。

Python中的代码块:

代码块:一个模块,一个函数,一个类,一个文件等都是一个代码块。

而作为交互方式输入的每个命令都是一个代码块。

代码块的缓存机制:Python在执行同一个代码块的初始化对象的命令时,会检查是否其值是否已经存在,如果存在,会将其重用。Python在同一个代码块中,初始化对象时,首先将初始化的这个变量与值存储在一个字典中,在遇到新的变量时,会先在字典中查询记录,如果有同样的记录那么它会重复使用这个字典中的之前的这个值。所以如果a=10,b=10在同一代码块中,那么变量a和b引用同一块内存的值10即满足缓存机制则他们在内存中只存在一个,即:id相同。

代码块的缓存机制的适用范围: int(float),str,bool。

int(float):任何数字在同一代码块下都会复用。

bool:True和False在字典中会以1,0方式存在,并且复用。

str:几乎所有的字符串都会符合缓存机制,具体规定如下:

如:字符串满足代码块的缓存机制:

str_1 = "abcdefg_!@#$%^&"
str_2 = "abcdefg_!@#$%^&"
print(str_1 is str_2)
# 打印内容如下 True

在同一代码块下所有字母,数字和bool值符合代码块的缓存机制。在不同代码块下就按照小数据池的缓存机制。 

代码块缓存机制的优点:在需要值相同的字符串,整数的时候,直接从‘字典’中取出复用,避免频繁的创建和销毁,提升效率,节约内存。

小数据池:也称为小整数缓存机制,或者称为驻留机制等等。

小数据池只针对 int(float),str,bool。

小数据池是针对不同代码块之间的缓存机制!!!

Python自动将-5~256的整数进行了缓存,当你将这些整数赋值给变量时,并不会重新创建对象,而是使用已经创建好的缓存对象。

python会将一定规则的字符串在字符串驻留池中,创建一份,当你将这些字符串赋值给变量时,并不会重新创建对象, 而是使用在字符串驻留池中创建好的对象。

优点:与代码块缓存机制类似,需要值相同的字符串,整数的时候,直接从‘池’里拿来用,避免频繁的创建和销毁,提升效率,节约内存。

int小数据池的范围是-5~256 ,如果多个变量都是指向同一个(在这个范围内的)数字,他们在内存中指向的都是一个内存地址。

当变量a和b的值都为-5时:

 

当变量a和b的值都为-6时

 

可以发现它们的内存地址不一样了,也就是不符合小数据池的缓存机制了。

当变量a和b的值都为256时

由上图我们可以看出,符合小数据池的缓存机制

当变量a和b的值都为257时

 

它们的内存地址不一样了,也就是不符合小数据池的缓存机制了。

所以小数据池整数范围是-5到256之间。

str:字符串由字母,数字,下划线组成:

当变量的值由大小写字母,数字,下划线时,默认驻留。

 

bool值:True,False,无论你创建多少个变量指向True,False,那么他在内存中引用的都是一个地址。

  如果在同一代码块下,则采用同一代码块下的换缓存机制。

  如果是不同代码块,则采用小数据池的驻留机制。

.


下一篇:文件操作:https://www.cnblogs.com/caesar-id/p/10266313.html

原文地址:https://www.cnblogs.com/caesar-id/p/10252202.html