字符编码问题是经常困扰python编程人员的问题,我在编写爬虫的过程中也经常遇到这个头疼的事。 从python3开始,明确区分了人类语言(文本字符串)和机器语言(二进制字节),咱们先说文本字符串 开始之前,得对"字符"进行定义: 字符:Unicode字符,从python3的str对象中获取的元素是Unicode字符 字符串:字符串就是一个字符序列(这里对于(一)中内容相呼应) if __name__ == " __main__ " : # 创建字符 s1 = str( ' a ' ) s2 = ' b ' s3 = u ' c ' print (s1, s2, s3) # a b c
python3中内置有两种基本的二进制序列类型:不可变的bytes和可变bytearray (1)bytes和bytearray的各个元素是介于0~255(8个bit)之间的整数; (2)二进制序列的切片始终是同一类型的二进制序列 if __name__ == " __main__ " : # 创建bytes 和 bytearray b1 = bytes( ' abc你好 ' , encoding= ' utf8 ' ) # 关于encode稍后会说,不知道有没有人和我一样总是将编码与解码的方向混淆 print (b1) # b'abc\xe4\xbd\xa0\xe5\xa5\xbd' b2 = bytearray( ' abc你好 ' , encoding= ' utf8 ' ) print (b2) # bytearray(b'abc\xe4\xbd\xa0\xe5\xa5\xbd') # 切片(提示:序列都可以切片) print (b1[3:5]) # b'\xe4\xbd' print (b2[3:5]) # bytearray(b'\xe4\xbd') # 使用列表取值的方法试试 print (b1[3]) # 228 此时取出来的就不是字节序列了,而是一个元素 for _ in b1: print (_, end= ' , ' ) # 97,98,99,228,189,160,229,165,189, 这都是8bit的整数 # bytes的不可变 vs. bytearray的可变 # b1[3] = 160 # 报错:'bytes' object does not support item assignment print (id(b2), b2) # 4373768376 bytearray(b'abc\xe4\xbd\xa0\xe5\xa5\xbd') b2[2] = 78 print (id(b2), b2) # 4373768376 bytearray(b'abN\xe4\xbd\xa0\xe5\xa5\xbd') # 将b2转换成字符串看看 print (b2.decode( ' utf8 ' )) # abN你好 # 注意,这里之所以能够用utf8转成unicode,是因为N的ascii码和utf8一致 b2.extend(bytearray( ' 添加的内容 ' , encoding= ' utf8 ' )) # 既然是可变序列,bytearray当然拥有一般的序列的方法 print (id(b2), b2) # 4373768376 bytearray(b'abN\xe4\xbd\xa0\xe5\xa5\xbd\xe6\xb7\xbb\xe5\x8a\xa0\xe7\x9a\x84\xe5\x86\x85\xe5\xae\xb9') print (b2.decode( ' utf8 ' )) # abN你好添加的内容 # PS:大家可以将二进制序列当成列表,元素就是ascii编码(0~255) struct可以从二进制序列中提取结构化信息。 struct模块提供了一些函数,可以将打包的字节序列转换成不同类型字段组成的元组;还有一些函数用于执行反向转换。 struct模块可以处理bytes、bytearray、memoryview对象。 import struct if __name__ == " __main__ " : # memoryview类用于共享内存,可以访问其他二进制序列、打包的数组和缓冲中的数据切片,该操作无需赋值字节序列 fmt = ' <3s3sHH ' # 设置格式,< 是小字节序,3s3s是两个3字节序列,HH是两个16位二进制整数 with open( ' L3_图_python.jpg ' , ' rb ' ) as f: # 需要在github中下载后运行 img = memoryview(f.read()) print (bytes(img[:10])) # b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x02\x00\x1c\x00\x1c\x00\x00' print (struct.unpack(fmt, img[:10])) # (b'\xff\xd8\xff', b'\xe0\x00\x10', 17994, 17993) :拆包 del img python自带有超过100中编解码器,用于在字符串和字节之间相互转换。 每个编码都有多个名称,例如'utf_8'、'utf8'、'utf-8'、'U8',这些都可以传递给open()、str.encode()、bytes.decode()中的 encoding参数 if __name__ == " __main__ " : # 看看不同的编码效果 for codec in [ ' gbk ' , ' utf8 ' , ' utf16 ' ]: print (codec, " 你好 " .encode(codec), sep= ' \t ' ) gbk b'\xc4\xe3\xba\xc3' utf8 b'\xe4\xbd\xa0\xe5\xa5\xbd' utf16 b'\xff\xfe`O}Y' # 咱们再来解码 print (b ' \xc4\xe3\xba\xc3 ' .decode( ' gbk ' )) # 你好 print (b ' \xe4\xbd\xa0\xe5\xa5\xbd ' .decode( ' utf8 ' )) # 你好 print (b ' \xff\xfe`O}Y ' .decode( ' utf16 ' )) # 你好 # (1)UnicodeEncodeError # 使用errors参数 s1 = " hello,你长胖啦 " .encode( ' latin-1 ' , errors= ' ignore ' ) print (s1) # b'hello' 使用 errors='ignore' 忽略了无法编码的字符 s2 = " hello,你长胖啦 " .encode( ' latin-1 ' , errors= ' replace ' ) print (s2) # b'hello?????' 使用errors='replace'将无法编码的字符用问好代替 s3 = " hello,你长胖啦 " .encode( ' latin-1 ' , errors= ' xmlcharrefreplace ' ) print (s3) # b'hello&#65292;&#20320;&#38271;&#32982;&#21862;' 使用errors='xmlcharrefreplace'将无法编码的内容替换成XML实体 # (2) UnicodeDecodeError # 乱码字符称为鬼符,以下实例演示出现鬼符的情况 s4 = b ' Montr\xe9al ' print (s4.decode( ' cp1252 ' )) # Montréal print (s4.decode( ' iso8859_7 ' )) # Montrιal print (s4.decode( ' koi8_r ' )) # MontrИal # print(s4.decode('utf8')) # 报错:UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 5: invalid continuation byte print (s4.decode( ' utf8 ' , errors= ' replace ' )) # Montr�al for expression in expressions.split(): value = eval(expression) print (expression.rjust(30), ' -> ' , repr(value)) 我电脑运行结果如下: (' locale.getpreferredencoding()', '->', "'UTF-8'") (' type(my_file)', '->', "<type 'file'>") (' my_file.encoding', '->', 'None') (' sys.stdout.isatty()', '->', 'True') (' sys.stdout.encoding', '->', "'UTF-8'") (' sys.stdin.isatty()', '->', 'True') (' sys.stdin.encoding', '->', "'UTF-8'") (' sys.stderr.isatty()', '->', 'True') (' sys.stderr.encoding', '->', "'UTF-8'") (' sys.getdefaultencoding()', '->', "'ascii'") (' sys.getfilesystemencoding()', '->', "'utf-8'") 我们发现café可以用'café'和'cafe\u0301'两种方式表示,这个词对于人来说是一样的,但是这两种表示对于计算机来说确实不一样的 向这样的序列叫"标准等价物",在计算机中存储的值不相等,但应用程序应该认为相等。 要解决这个问题,需要用到unicodedata.nomalize函数,它的第一个参数可以选择这四种形式的一个:"NFC"、"NFD"和"NFKC"、"NFKD" "NFC":使用最少的码为构成等价的字符串 "NFD":把组合的字符分割成基本字符和单独的组合字符 "NFKC"&"NFKD":这两种是较严格的规范形式,对"兼容字符有影响" from unicodedata import normalize if __name__ == " __main__ " : # "NFC" & "NFD" print (s1.encode( ' utf8 ' ), s2.encode( ' utf8 ' )) # b'caf\xc3\xa9' b'cafe\xcc\x81' s1 = normalize( " NFC " , s1) s2 = normalize( " NFC " , s2) print (s1, s2) # café café print (s1 == s2) # True print (s1.encode( ' utf8 ' ), s2.encode( ' utf8 ' )) # b'caf\xc3\xa9' b'caf\xc3\xa9' # "NFKC"&"NFKD" # 这两种方式会损失信息,所以不建议使用,除非一些特殊情况,比如搜索和索引中 s3 = ' ½ ' print (normalize( ' NFKC ' , s3)) # 1⁄2 将½转换成了1⁄2 s4 = ' ' print (normalize( ' NFKC ' , s4)) # TM 将™转换成了TM 另外,如果比较的时候不区分大小写,建议使用str.casefold(), 它与lower基本一致,其中大约有116个特殊的字符结果不同 s5 = ' AKJkakshfKHDSdshfKSDShKkHkjhKJkgJhgJHkkHkjhJKhKJhK ' print (s5.casefold()) # akjkakshfkhdsdshfksdshkkhkjhkjkgjhgjhkkhkjhjkhkjhk python比较序列时,会一一比较其中的元素。对于字符来说,比较的是其码位,主要是比较的ascii码; 非ascii文本的标准排序方式是使用locale.strxfrm函数,但是使用这个函数必须事先设定区域,但有些操作系统不支持,并且改变区域设置并不十分合适 建议使用pyuca.Collator.sort_key方法进行排序 if __name__ == " __main__ " : # python默认的排序 fruits = [ ' caju ' , ' atemoia ' , ' cajá ' , ' açaí ' , ' acerola ' ] print (sorted(fruits)) # ['acerola', 'atemoia', 'açaí', 'caju', 'cajá'] # 但正确排序应该是:['açaí','acerola', 'atemoia', 'cajá', 'caju'] # 使用pyuca.Collator.sort_key import pyuca print (sorted(fruits, key = pyuca.Collator().sort_key)) # ['açaí', 'acerola', 'atemoia', 'cajá', 'caju'] # pyuca可以将自定义排序表路径传递给Collator()构造方法,pyuca默认使用自带的allkeys.txt