Python基础(二)深浅拷贝和编码

 

一、Python的深浅拷贝

1、浅拷贝

 1 name1=['张三','李四','王五','赵六',[1,2,3]]
 2 name2=name1.copy()
 3 print(name1,name2)
 4 # ['张三', '李四', '王五', '赵六', [1, 2, 3]] ['张三', '李四', '王五', '赵六', [1, 2, 3]]
 5 name1[1] = 'wanstack'
 6 print(name1,name2)
 7 # ['张三', 'wanstack', '王五', '赵六', [1, 2, 3]] ['张三', '李四', '王五', '赵六', [1, 2, 3]]
 8 # 查看结果,是我们想想中的样子,name1元素被修改了,name2的内容不变
 9 
10 name1[4][1] = 'fish'
11 print(name1,name2)
12 # ['张三', 'wanstack', '王五', '赵六', [1, 'fish', 3]] ['张三', '李四', '王五', '赵六', [1, 'fish', 3]]
13 # 上面的结果和我们想想的不一样我们修改了name1,name2也跟着变了
14 
15 # 这里就涉及到我们要讲的深浅拷贝了
16 #不可变数据类型:数字,字符串,元组         可变类型:列表,字典
17 
18 # 当你对可变类型进行修改时,比如这个列表对象name1,它的内存地址不会变化,注意是这个列表对象name1,不是它里面的元素
19 print(id(name1))
20 name1[1] = 'test'
21 print(id(name1))
22 """
23 6555688
24 6555688
25 """
26 # 像字符串,元组,数字这些不可变数据类型,,是不能修改的,比如我想要一个'fish'的字符串,
27 # 只能重新创建一个'fish'的对象,然后让指针只想这个新对象
28 a = 'wanstack'
29 print(id(a))
30 a = 'fish'
31 print(id(a))
32 """
33 49808352
34 20358080
35 """
36 # a[1] = 'e'  # 报错如下
37 # TypeError: 'str' object does not support item assignment
38 print('*'*30)
39 # 浅拷贝
40 a=[[1,2],3,4]
41 b=a[:]#b=a.copy()
42 print(a,b)
43 print(id(a),id(b))
44 print('*'*30)
45 print('a[0]:{a},b[0]:{b}'.format(a=id(a[0]),b=id(b[0])))
46 print('a[0][0]:',id(a[0][0]),'b[0][0]:',id(b[0][0]))
47 print('a[0][1]:',id(a[0][1]),'b[0][1]:',id(b[0][1]))
48 print('a[1]:',id(a[1]),'b[1]:',id(b[1]))
49 print('a[2]:',id(a[2]),'b[2]:',id(b[2]))
50 """
51 ******************************
52 [[1, 2], 3, 4] [[1, 2], 3, 4]
53 54136624 54136664
54 ******************************
55 a[0]:54135184,b[0]:54135184
56 a[0][0]: 498947824 b[0][0]: 498947824
57 a[0][1]: 498947840 b[0][1]: 498947840
58 a[1]: 498947856 b[1]: 498947856
59 a[2]: 498947872 b[2]: 498947872
60 未修改之前:
61 从上面的结果中可以看到2个大列表的id是一样的。2个小列表的id是一样的。小列表中的元素id是一样的。
62 唯独大列表中的其他元素不一样.所以修改小列表中的元素是会影响其他的
63 """
64 
65 print('#'*30)
66 b[0][0]=8
67 print(a,b)
68 print(id(a),id(b))
69 print('a[0]:',id(a[0]),'b[0]:',id(b[0]))
70 print('a[0][0]:',id(a[0][0]),'b[0][0]:',id(b[0][0]))
71 print('a[0][1]:',id(a[0][1]),'b[0][1]:',id(b[0][1]))
72 print('a[1]:',id(a[1]),'b[1]:',id(b[1]))
73 print('a[2]:',id(a[2]),'b[2]:',id(b[2]))
74 """
75 [[8, 2], 3, 4] [[8, 2], 3, 4]
76 42819752 42819792
77 a[0]: 42797616 b[0]: 42797616
78 a[0][0]: 498292576 b[0][0]: 498292576
79 a[0][1]: 498292480 b[0][1]: 498292480
80 a[1]: 498292496 b[1]: 498292496
81 a[2]: 498292512 b[2]: 498292512
82 """
View Code

 

2、深拷贝

深拷贝就是开辟2个独立的内存空间

 1 # 深拷贝
 2 import copy
 3 a=[[1,2],3,4]
 4 b = copy.deepcopy(a)
 5 print("="*30)
 6 print(a,b)
 7 print(id(a),id(b))
 8 print('*'*30)
 9 print('a[0]:{a},b[0]:{b}'.format(a=id(a[0]),b=id(b[0])))
10 print('a[0][0]:',id(a[0][0]),'b[0][0]:',id(b[0][0]))
11 print('a[0][1]:',id(a[0][1]),'b[0][1]:',id(b[0][1]))
12 print('a[1]:',id(a[1]),'b[1]:',id(b[1]))
13 print('a[2]:',id(a[2]),'b[2]:',id(b[2]))
14 """
15 ==============================
16 [[1, 2], 3, 4] [[1, 2], 3, 4]
17 50630448 50356832
18 ******************************
19 a[0]:50630208,b[0]:50668408
20 a[0][0]: 500913904 b[0][0]: 500913904 # 看到区别了吗,id不一样了,说明开辟了2个内存空间
21 a[0][1]: 500913920 b[0][1]: 500913920 #  看到区别了吗,id不一样了
22 a[1]: 500913936 b[1]: 500913936
23 a[2]: 500913952 b[2]: 500913952
24 
25 """
26 
27 print('-'*30)
28 b[0][0]=8
29 print(a,b)
30 print(id(a),id(b))
31 print('a[0]:',id(a[0]),'b[0]:',id(b[0]))
32 print('a[0][0]:',id(a[0][0]),'b[0][0]:',id(b[0][0]))
33 print('a[0][1]:',id(a[0][1]),'b[0][1]:',id(b[0][1]))
34 print('a[1]:',id(a[1]),'b[1]:',id(b[1]))
35 print('a[2]:',id(a[2]),'b[2]:',id(b[2]))
36 """
37 ------------------------------
38 [[1, 2], 3, 4] [[8, 2], 3, 4]  # 看到结果了吗?和我们想的一样了
39 11898832 11625216
40 a[0]: 11898592 b[0]: 11916312
41 a[0][0]: 506353392 b[0][0]: 506353504
42 a[0][1]: 506353408 b[0][1]: 506353408
43 a[1]: 506353424 b[1]: 506353424
44 a[2]: 506353440 b[2]: 506353440
45 
46 """
View Code

 

 

二、Python编码

1、编码介绍

首先,我们从一段信息即消息说起,消息以人类可以理解、易懂的表示存在。我打算将这种表示称为“明文”(plain text)。对于说英语的人,纸张上打印的或屏幕上显示的英文单词都算作明文。

其次,我们需要能将明文表示的消息转成另外某种表示,我们还需要能将编码文本转回成明文。从明文到编码文本的转换称为“编码”,从编码文本又转回成明文则为“解码”。

编码问题是个大问题,如果不彻底解决,它就会像隐藏在丛林中的小蛇,时不时地咬你一口。
    那么到底什么是编码呢?

    //ASCII

    记住一句话:计算机中的所有数据,不论是文字、图片、视频、还是音频文件,本质上最终都是按照类似 01010101 的二进制存储的。
    再说简单点,计算机只懂二进制数字!
    所以,目的明确了:如何将我们能识别的符号唯一的与一组二进制数字对应上?于是美利坚的同志想到通过一个电平的高低状态来代指0或1,
    八个电平做为一组就可以表示出
    256种不同状态,每种状态就唯一对应一个字符,比如A--->00010001,而英文只有26个字符,算上一些特殊字符和数字,128个状态也够
    用了;每个电平称为一个比特为,约定8个比特位构成一个字节,这样计算机就可以用127个不同字节来存储英语的文字了。这就是ASCII编码。
    
    扩展ANSI编码
    刚才说了,最开始,一个字节有八位,但是最高位没用上,默认为0;后来为了计算机也可以表示拉丁文,就将最后一位也用上了,
    从128到255的字符集对应拉丁文啦。至此,一个字节就用满了!

    //GB2312

    计算机漂洋过海来到中国后,问题来了,计算机不认识中文,当然也没法显示中文;而且一个字节所有状态都被占满了,万恶的帝国主义亡
    我之心不死啊!我党也是棒,自力更生,自己重写一张表,直接生猛地将扩展的第八位对应拉丁文全部删掉,规定一个小于127的字符的意
    义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节
   (低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了;这种汉字方案叫做 “GB2312”。GB2312 是对 ASCII 的中文扩展。

    //GBK 和 GB18030编码

    但是汉字太多了,GB2312也不够用,于是规定:只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的
    内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。

    //UNICODE编码:

    很多其它国家都搞出自己的编码标准,彼此间却相互不支持。这就带来了很多问题。于是,国际标谁化组织为了统一编码:提出了标准编码准
    则:UNICODE 。
    UNICODE是用两个字节来表示为一个字符,它总共可以组合出65535不同的字符,这足以覆盖世界上所有符号(包括甲骨文)

    //utf8:

    unicode都一统天下了,为什么还要有一个utf8的编码呢?
    大家想,对于英文世界的人们来讲,一个字节完全够了,比如要存储A,本来00010001就可以了,现在吃上了unicode的大锅饭,
    得用两个字节:00000000 00010001才行,浪费太严重!
    基于此,美利坚的科学家们提出了天才的想法:utf8.
    UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,它可以使用1~4个字节表示一个符号,根据
    不同的符号而变化字节长度,当字符在ASCII码的范围时,就用一个字节表示,所以是兼容ASCII编码的。

    这样显著的好处是,虽然在我们内存中的数据都是unicode,但当数据要保存到磁盘或者用于网络传输时,直接使用unicode就远不如utf8省空间啦!
    这也是为什么utf8是我们的推荐编码方式。

    Unicode与utf8的关系:
    一言以蔽之:Unicode是内存编码表示方案(是规范),而UTF是如何保存和传输Unicode的方案(是实现)这也是UTF与Unicode的区别。

补充:utf8是如何节约硬盘和流量的

s="I'm 汉子"

你看到的unicode字符集是这样的编码表:

I  0049
'  0027
m  006d
   0020
汉 82d1
子 660a

每一个字符对应一个十六进制数字。
计算机只懂二进制,因此,严格按照unicode的方式(UCS-2),应该这样存储:

I      00000000 01001001
'      00000000 00100111
m      00000000 01101101
       00000000 00100000
汉     10000010 11010001
子     01100110 00001010

这个字符串总共占用了12个字节,但是对比中英文的二进制码,可以发现,英文前9位都是0!浪费啊,浪费硬盘,浪费流量。怎么办?UTF8:

I    01001001
'    00100111
m    01101101
     00100000
汉   11101000 10001011 10010001
子   11100110 10011000 10001010

utf8用了10个字节,对比unicode,少了两个,因为我们的程序英文会远多于中文,所以空间会提高很多!

记住:一切都是为了节省你的硬盘和流量。

 

2、Python2的string编码

需知:

1.在python2默认编码是ASCII, python3里默认是unicode

2.unicode 分为 utf-32(占4个字节),utf-16(占两个字节),utf-8(占1-4个字节), so utf-16就是现在最常用的unicode版本, 不过在文件里存的还是utf-8,因为utf8省空间

3.在py3中encode,在转码的同时还会把string 变成bytes类型,decode在解码的同时还会把bytes变回string

 1 #-*-coding:utf-8-*-
 2 __author__ = 'Alex Li'
 3 
 4 import sys
 5 print(sys.getdefaultencoding())
 6 
 7 
 8 msg = "我爱北京天安门"
 9 msg_gb2312 = msg.decode("utf-8").encode("gb2312")
10 gb2312_to_gbk = msg_gb2312.decode("gbk").encode("gbk")
11 
12 print(msg)
13 print(msg_gb2312)
14 print(gb2312_to_gbk)
15 
16 in python2
Python2编码

 无论是utf8还是gbk都只是一种编码规则,一种把unicode数据编码成字节数据的规则,所以utf8编码的字节一定要用utf8的规则解码,否则就会出现乱码或者报错的情况。

经常出现的问题:

在python2中经常会出现如下报错:

1 print('汉子')  # 汉子'
2 # SyntaxError: Non-ASCII character '\xe8' in file E:/workspace/learning_python3/����ƪ/�ַ�����.py on line 4, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

 

 上面说没有ASCII字符串,也就是ASCII编码中不认识中文,因为python2中默认的编码格式是ASCII码。我们只需要指定默认的编码格式即可解决上述问题:

1 # coding:utf-8
2 print('汉子')  # 汉子'

 

 

 1 # coding:utf-8
 2 
 3 print '汉子'  #默认是utf-8
 4 print repr('汉子')
 5 print (u"hello"+"yuan")
 6 print (u'汉子'+'最帅')  # 这个是unicode编码
 7 
 8 """
 9 Traceback (most recent call last):
10   File "E:/workspace/learning_python3/����ƪ/�ַ�����.py", line 7, in <module>
11     print (u'汉子'+'最帅')
12 UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)
13 """
python2解码错误

 

 

 Python 2 悄悄掩盖掉了 byte 到 unicode 的转换,只要数据全部是 ASCII 的话,所有的转换都是正确的,一旦一个非 ASCII 字符偷偷进入你的程序,那么默认的解码将会失效,从而造成 UnicodeDecodeError 的错误。py2编码让程序在处理 ASCII 的时候更加简单。你复出的代价就是在处理非 ASCII 的时候将会失败。

 解决上述报错需要手工指定解码的格式是

 

1 # coding:utf-8
2 
3 print '汉子'  #默认是utf-8
4 print repr('汉子')
5 print (u"hello"+"yuan")
6 # a = u'汉子'+'最帅'.decode('ASCCI')  # Python2默认是这样转换的
7 a = u'汉子'+'最帅'.decode('utf-8')  # 解码成unicode编码
8 print(a)
9 print(repr(a))

 

 

3、Python3字符编码

 py3也有两种数据类型:str和bytes;  str类型存unicode数据,bytse类型存bytes数据,与py2比只是换了一下名字而已。

在py3中encode,在转码的同时还会把string 变成bytes类型,decode在解码的同时还会把bytes变回string

Python3中的string默认是unicode,bytes类型---> string(unicode)需要decode,string(unicode) ---> bytes需要encode成字节码。

 

 1 #-*-coding:gb2312 -*-   #这个也可以去掉
 2 
 3 
 4 import sys
 5 print(sys.getdefaultencoding())
 6 
 7 
 8 msg = "我爱北京天安门"
 9 #msg_gb2312 = msg.decode("utf-8").encode("gb2312")
10 msg_gb2312 = msg.encode("gb2312") #默认就是unicode,不用再decode,喜大普奔
11 gb2312_to_unicode = msg_gb2312.decode("gb2312")
12 gb2312_to_utf8 = msg_gb2312.decode("gb2312").encode("utf-8")
13 
14 print(msg)
15 print(msg_gb2312)
16 print(gb2312_to_unicode)
17 print(gb2312_to_utf8)
18 
19 in python3

 

 Python 3最重要的新特性大概要算是对文本和二进制数据作了更为清晰的区分,不再会对bytes字节串进行自动解码。文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示。Python 3不会以任意隐式的方式混用str和bytes,正是这使得两者的区分特别清晰。你不能拼接字符串和字节包,也无法在字节包里搜索字符串(反之亦然),也不能将字符串传入参数为字节包的函数(反之亦然)

注意:无论py2,还是py3,与明文直接对应的就是unicode数据,打印unicode数据就会显示相应的明文(包括英文和中文)。

 

4、文件从磁盘到内存编码

当我们在word上编辑文字的时候,不管是中文还是英文,计算机都是不认识的,那么在保存之前数据是通过什么形式存在内存的呢? 就是unicode数据,为什么要存unicode数据,这是因为它的名字最屌:万国码!解释起来就是无论英文,中文,日文,拉丁文,世界上的任何字符它都有唯一编码对应,所以兼容性是最好的。

好,那当我们保存了存到磁盘上的数据又是什么呢?答案是通过某种编码方式编码的bytes字节串。比如utf8---一种可变长编码,很好的节省了空间;当然还有历史产物的gbk编码等等。于是,在我们的文本编辑器软件都有默认的保存文件的编码方式,比如utf8,比如gbk。当我们点击保存的时候,这些编辑软件已经"默默地"帮我们做了编码工作。

那当我们再打开这个文件时,软件又默默地给我们做了解码的工作,将数据再解码成unicode,然后就可以呈现明文给用户了!所以,unicode是离用户更近的数据,bytes是离计算机更近的数据。

说了这么多,和我们程序执行有什么关系呢?

先明确一个概念:py解释器本身就是一个软件,一个类似于文本编辑器一样的软件!

上述的大概流程是: 我们使用unicode再内存中修改着我们的文档--->当修改完成保存到磁盘上文本编辑器给我们使用utf-8编码(encode)成计算机可以理解的字节码。---> 当我们需要从磁盘中打开文档时,文本编辑器给我们解码(decode)成unicode,人类可以理解的文字。

 

现在让我们一起还原一个py文件从创建到执行的编码过程:

打开pycharm,创建hello.py文件,写入

 1 ret=1+1 2 s='汉子' 3 print(s) 

 

1) 当我们保存的的时候,hello.py文件就以pycharm默认的编码方式(例如utf-8)保存到了磁盘; encode成字节码到磁盘

2) 当我们打开hello.py文件的时候,pycharm就再以默认的编码方式(utf-8)对读到该文件的内容进行解码,转成unicode到内存我们就看到了我们的明文;decode成unicode

3) 当我们点击运行按钮或者在命令行运行该文件时,py解释器这个软件就会被调用,打开文件,然后解码(decode) 存在磁盘上的bytes数据成unicode数据,这个过程和编辑器是一样的

不同的是py解释器会再将这些unicode数据翻译成C代码再转成二进制的数据流,最后通过控制操作系统调用cpu来执行这些二进制数据,整个过程才算结束。

 

那么问题来了,我们的文本编辑器有自己默认的编码解码方式,我们的解释器有吗?

当然有啦,py2默认ASCII码,py3默认的unicode,可以通过如下方式查询

import sys
print(sys.getdefaultencoding())

 

再python2中

大家还记得这个声明吗?

#coding:utf8

是的,这就是因为如果py2解释器去执行一个utf8编码的文件,就会以默认地ASCII去解码utf8,一旦程序中有中文,自然就解码错误了,所以我们在文件开头位置声明 #coding:utf-8,其实就是告诉解释器,你不要以默认的编码方式去解码这个文件,而是以utf8来解码。而py3的解释器因为默认unicode编码,所以就方便很多了。

 

注意:我们上面讲的string编码是在cpu执行程序时的存储状态,是另外一个过程,不要混淆!

 

5、常见的编码问题

1 cmd下的乱码问题

hello.py

#coding:utf8
print ('汉子')

文件保存时的编码也为utf8。

思考:为什么在IDE下用2或3执行都没问题,在cmd.exe下3正确,2乱码呢?

      我们在win下的终端即cmd.exe去执行,大家注意,cmd.exe本身也一个软件;当我们python2 hello.py时,python2解释器(默认ASCII编码)去按声明的utf8编码文件,而文件又是utf8保存的,所以没问题;问题出在当我们print'汉子'时,解释器这边正常执行,也不会报错,只是print的内容会传递给cmd.exe用来显示,而在py2里这个内容就是utf8编码的字节数据,可这个软件默认的编码解码方式是GBK,所以cmd.exe用GBK的解码方式去解码utf8自然会乱码。

大概的流程如下:  cmd.exe 调用py2去encode('utf-8') 去编码hello.py文件,文件默认是utf-8保存所以没问题--->执行print时 解释器decode成utf-8的也可以正常显示执行。但是cmd.exe默认是gbk的编码和解码所以出现乱码。

py3正确的原因是传递给cmd的是unicode数据,cmd.exe可以识别内容,所以显示没问题。

明白原理了,修改就有很多方式,比如:

print (u'汉子')

改成这样后,cmd下用2也不会有问题了。

2 open()中的编码问题

创建一个hello文本,保存成utf8:

汉子,你最帅!

 

同目录下创建一个index.py

 1 f=open('hello') 2 print(f.read()) 

为什么 在linux下,结果正常:汉子,在win下,乱码:鑻戞槉(py3解释器)?

因为你的win的操作系统安装时是默认的gbk编码,而linux操作系统默认的是utf8编码;

当执行open函数时,调用的是操作系统打开文件,操作系统用默认的gbk编码去解码utf8的文件,自然乱码。

解决办法:

f=open('hello',encoding='utf8')
print(f.read())

如果你的文件保存的是gbk编码,在win 下就不用指定encoding了。

另外,如果你的win上不需要指定给操作系统encoding='utf8',那就是你安装时就是默认的utf8编码或者已经通过命令修改成了utf8编码。

注意:open这个函数在py2里和py3中是不同的,py3中有了一个encoding=None参数。

 

posted @ 2017-06-02 16:15  wanstack  阅读(203)  评论(0)    收藏  举报