对于想了解Python中的编码的读者,本文将是一篇不可错过的文章,我们将详细介绍python中的编码和解码,并且为您提供关于Python2中的编码问题、Python3中的编码、pythonurllib
对于想了解Python中的编码的读者,本文将是一篇不可错过的文章,我们将详细介绍python中的编码和解码,并且为您提供关于Python 2 中的编码问题、Python 3中的编码、python urllib中的编码处理示例、python 中的编码解码的有价值信息。
本文目录一览:Python中的编码(python中的编码和解码)
编码中的相关术语
- ACR Abstract Character Repertoire
- the set of characters to be encoded, for example, some alphabet or symbol set
- CCS Coded Character Set
- a mapping from an abstract character repertoire to a set of nonnegative integers
- CEF Character Encoding Form
- a mapping from a set of nonnegative integers that are elements of a CCS to a set of sequences of particular code units of some specified width, such as 32-bit integers
- CES Character Encoding Scheme
- a reversible transformation from a set of sequences of code units (from one or more CEFs to a serialized sequence of bytes)
- Code Point Nonnegative integer in the CCS
- Code Unit The minimal bit combination that can represent a unit of encoded text for processing or interchange. The Unicode Standard uses 8-bit code units in the UTF-8 encoding form, 16-bit code units in the UTF-16 encoding form, and 32-bit code units in the UTF-32 encoding form
对于汉字,使用unicode编码字符集,这样就可以得到每个中文字符对于的码点(code point)。然后选择使用UTF-8字符编码表,这样就确定了每个码点对应的码元(code unit)。最终使用UTF-8字符编码格式,将码元编码成字节序列。
比如中文字符汉,它的unicode码点是U+6C49,因为UTF-8的码元是8位,对于U+6C49,需要使用3个字节(24位)来进行编码,最终编码为11100110 10110001 10001001。
根据UTF-8编码规则,取第一个字节的后4位,取第二个字节的后6位,取第三个字节的后6位,得到0110 110001 001001,将其转位16进制刚好就是6C49。
Unicode
Unicode根据字符的使用频率将字符划分到17个平面中,每个平面有2<sup>16</sup>=65536个码点,所以一共有17 * 2<sup>16</sup> = 1,114,112码点。图1为Unicode平面,图2为Basic Multilingual Plane(第0个平面),也是最常用的平面。
<figure> <img src="https://img2018.cnblogs.com/blog/1595747/201904/1595747-20190404151922870-739933317.png" /> <p>图1:Unicode平面</p> </figure>
<figure> <img src="https://img2018.cnblogs.com/blog/1595747/201904/1595747-20190404151930044-1566546857.png" /> <p>图2:Basic Multilingual Plane</p> </figure>
前面提到的汉的码点为U+6C49,我们可以在图2中发现,是符合CJK(中日韩统一表意文字)字符的码点范围的。
UTF-8
在前面提及UTF-8的时候,强调了UTF-8字符编码表(CEF)和UTF-8字符编码格式(CES)。在没有限定条件下,可以使用UTF-8模糊这两个概念。但是实际上还是有区别的:
It is important not to confuse a Character Encoding Form (CEF) and a CES.
- The CEF maps code points to code units, while the CES transforms sequences of code units to byte sequences. (For a direct mapping from characters to serialized bytes, see Section 6 Character Maps.)
- The CES must take into account the byte-order serialization of all code units wider than a byte that are used in the CEF.
- Otherwise identical CESs may differ in other aspects, such as the number of user-defined characters allowed. (This applies in particular to the IBM CDRA architecture, which may distinguish host CCSIDs based on whether the set of UDC''s is conformably convertible to the corresponding code page or not.)
UTF-8 是一种变长的编码方式,一般用 1~4 个字节序列来表示 Unicode 字符,也是目前应用最广泛的一种 Unicode 编码方式,但是它不是最早的 Unicode 编码方式,最早的 Unicode 编码方式是 UTF-16。UTF-8 编码算法有以下特点:
- 首字节码用来区分采用的编码字节数,如果首字节以 0 开头,表示单字节编码;如果首字节以 110 开头,表示双字节编码;如果首字节以 1110 开头,表示三字节编码,以此类推
- 除了首字节码外,用 10 开头表示多字节编码的后续字节
- 与 ASCII 编码方式完全兼容:U+0000 到 U+007F 范围内(十进制为 0~127)的 Unicode 码点值所对应的字符就是 ASCII 字符集中的字符,用一个字节表示,编码方式和 ASCII 编码一致
- 无字节序,在 UFT-8 编码格式的文本中,如果添加了 BOM,则标示该文本是由 UTF-8 编码方式编码的,而不用来说明字节序
<figure> <img src="https://www.ibm.com/developerworks/cn/java/unicode-programming-language/image003.jpg" /> <p>图3:UTF-8编码方式</p> </figure>
UTF-16和US-2
UTF-16 是最早的 Unicode 字符集编码方式,在概述 UTF-16 之前,需要解释一下 USC-2 编码方式,他们有源远流长的关系,UTF-16 源于 UCS-2。UCS-2 将字符编号(同 Unicode 中的码点)直接映射为字符编码,亦即字符编号就是字符编码,中间没有经过特别的编码算法转换。
UCS-2 编码方式只覆盖基本多语言平面的码点,因为 16 位二进制表示的最大值为 0xFFFF,而对于增补平面中的码点(范围为 0x10000~0x10FFFF,十进制为 65536~1114111),两字节的 16 位二进制是无法表示的。为了解决这个问题,The Unicode Consortium 提出了通过代理机制来扩展原来的 UCS-2 编码方式,也就是 UTF-16。
所以扩展UCS-2,采用代理对(surrogate pair)对unicode编码:用两个基本平面中未定义字符的码点合起来为增补平面中的码点编码,基本平面中这些用作"代理"的码点区域就被称之为"代理区(Surrogate Zone)",其码点编号范围为 0xD800~0xDFFF(十进制 55296~57343),共 2048个码点。代理区的码点又被分为高代理码点和低代理码点,高代理码点的取值范围为 0xD800~0xDBFF,低代理码点的取值范围为 0xDC00~0xDFFF,高代理码点和低代理码点合起来就是代理对,刚好可以表示增补平面内的所有码(1024 * 1024)。
UTF-32和US-4
UTF-32 是一个以固定四字节编码方式,ISO 10646 中称其为 UCS-4 的编码机制的子集。优点是每一个 Unicode 码点都和 UTF-16 的 Code Unit 一一对应,程序中如果采用 UTF-32 处理起来比较简单,但是所有的字符都用四个字节,特别浪费空间,所以实际上使用比较少。
Python2.x的unicode
相同的代码,在Centos6.5和Windows10系统中执行的结果却不一样。
Centos6.5:
# python
Python 2.7.5 (default, Apr 11 2018, 07:36:10)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import re
>>> re.compile(u''[\U0001F300-\U0001F64F]'', re.UNICODE)
<_sre.SRE_Pattern object at 0x7f337690c588>
>>>
Windows10:
> python
Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import re
>>> re.compile(u''[\U0001F300-\U0001F64F]'', re.UNICODE)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "D:\Temp\tools\venv_dev\lib\re.py", line 194, in compile
return _compile(pattern, flags)
File "D:\Temp\tools\venv_dev\lib\re.py", line 251, in _compile
raise error, v # invalid expression
sre_constants.error: bad character range
PyUnicodeObject
在源码中,PyUnicodeObject
的结构:
typedef struct {
PyObject_HEAD
Py_ssize_t length; /* Length of raw Unicode data in buffer */
Py_UNICODE *str; /* Raw Unicode buffer */
long hash; /* Hash value; -1 if not set */
PyObject *defenc; /* (Default) Encoded version as Python
string, or NULL; this is used for
implementing the buffer protocol */
} PyUnicodeObject;
unicode的值保存在Py_UNICODE
中,官方文档的解释:
This type represents the storage type which is used by Python internally as basis for holding Unicode ordinals. Python’s default builds use a 16-bit type for Py_UNICODE and store Unicode values internally as UCS2. It is also possible to build a UCS4 version of Python (most recent Linux distributions come with UCS4 builds of Python). These builds then use a 32-bit type for Py_UNICODE and store Unicode data internally as UCS4. On platforms where wchar_t is available and compatible with the chosen Python Unicode build variant, Py_UNICODE is a typedef alias for wchar_t to enhance native platform compatibility. On all other platforms, Py_UNICODE is a typedef alias for either unsigned short (UCS2) or unsigned long (UCS4).
在Windows10中,默认使用US-2,所以在编译正则表达式的时候将[\U0001F300-\U0001F64F]
转换为[\ud83c\udf00-\ud83d\ude4f]
,解析正则表达式的时候:
...
if sourcematch("-"):
# potential range
this = sourceget()
if this == "]":
if code1[0] is IN:
code1 = code1[1][0]
setappend(code1)
setappend((LITERAL, ord("-")))
break
elif this:
if this[0] == "\\":
code2 = _class_escape(source, this, nested + 1)
else:
code2 = LITERAL, ord(this)
if code1[0] != LITERAL or code2[0] != LITERAL:
raise error, "bad character range"
lo = code1[1]
hi = code2[1]
if hi < lo:
raise error, "bad character range"
setappend((RANGE, (lo, hi)))
...
这里的lo
是\udf00
,hi
是\ud83d
,因此hi<lo
,也就是这个范围是不合法的。所以报错了。
而在Centos6.5中,默认使用US-4编码,所以就是[\U0001F300-\U0001F64F]
,这当然是一个合法的范围。
Python2.x 遇到的常见编码错误
日志记录
我们线上服务的日志配置是使用的json文件,Python的json.loads
方法返回的字典的键值都是unicode字符串。所以在初始化logger的时候,formatter的fmt也是一个unicode字符串。比如在配置文件中配置的formatter:
{
"formatters": {
"access":{
"format":"%(asctime)s - %(message)s"
}
}
}
这样formatter的fmt就是u''%(asctime)s - %(message)s''
,而formatter是使用s = self._fmt % record.__dict__
来格式化日志的。所以在记录日志的时候,最好保证字符串和fmt是一样的类型。要么都是unicode
,要么都是str
。 因为在调用logger.error
的时候,我强制传入的是str
,但是json.loads
生成的fmt是unicode
类型的,所以抛出UnicodeDecodeError
错误。简而言之:
>>> u''%s %s'' % (u''中''.encode(''utf8''), ''a'')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: ''ascii'' codec can''t decode byte 0xe4 in position 0: ordinal not in range(128)
>>> ''%s %s'' % (u''中''.encode(''utf8''), ''a'')
''\xe4\xb8\xad a''
>>>
参考
- Unicode 及编码方式概述
- UNICODE CHARACTER ENCODING MODEL
- Emoji的编码以及常见问题处理
- [python]去掉 unicode 字符串前面的 u
- PEP 261 -- Support for "wide" Unicode characters
Python 2 中的编码问题
先来看一个异常信息:
UnicodeEncodeError: ''ascii'' codec can''t encode characters in position 51-52: ordinal not in range(128)
相信每个 Python 程序员对上面这个错误都再熟悉不过了,也许你这个问题的根源以及解决方法不是很清楚,那么这篇文章将尝试解答你心中的疑惑。
什么是字符串
Everything you thought you knew about strings is wrong.
计算机中,处理字符串是一个看似简单但及其复杂的问题。推荐我之前写的一文章《字符串,那些你不知道的事》。
Python 2 中的字符类型
Python 2 中有两种字符类型:str
与unicode
,其区别是:
str is text representation in bytes, unicode is text representation in characters.
字符字面量是str
类型,也就是说foo = "你好"
这一赋值语句表示的是把你好
所对应的二进制字节(这里的字节就是Python解释器读取源文件时读取到的)赋值给变量foo
,在 Python 2 中的str
类型相当于其他语言的byte
类型。
>>> "你好"
''\xe4\xbd\xa0\xe5\xa5\xbd''
unicode
对象保存的是字符的code point。在 Python 2 如果想表示 unicode
类型,有下面三种方式:
>>> u"你好"
u''\u4f60\u597d''
>>> "你好".decode("utf8")
u''\u4f60\u597d''
>>> unicode("你好", "utf8")
u''\u4f60\u597d''
Python 2 中的默认编码
sys.getdefaultencoding()
可以得到当前 Python 环境的默认编码,Python 2 中为ascii
。str
与unicode
两种字符类型中转化时,如果没有明确指定编码方式,就会用这个默认编码。
Python 2 中编码问题出现根源
了解了 Python 2 中的两种字符类型以及默认编码,现在就可以分析与编码相关的问题出现的原因了。
在 Python 2 的世界中,很多 API 对这两种字符类型的使用比较混乱,有的可以混用这两种,有的只能使用其中之一,如果在调用 API 时传入了错误的字符类型,Python 2 会自动去转为正确的字符类型,问题就出现在自动转化时用的编码默认是ascii
,所以经常会出现UnicodeDecodeError
或UnicodeEncodeError
错误了。
随着 unicode 的普及,Python 2 中越来越多的 API 使用 unicode
类型的字符串作为参数与返回值,我们在设计 API 时,也尽可能要使用unicode
类型。那是不是说,把程序里面的所有字符串都用unicode
类型表示,就不会出错了呢?也不尽然,一般有如下准则:
在进行文本处理(如查找一个字符串中字符的个数,分割字符串等)时,使用
unicode
类型在进行
I/O
处理(如,读写磁盘上的文件,打印一个字符串,网络通信等)时,使用str
类型
想想也很好理解,因为 Python 2 中的str
类型相当于其他语言的byte
类型,在进行I/O
时操作的是一个个的字节。
实战演练
知道了问题出现的原因,下面举一些常见的与编码相关的错误代码,演示如何正确的使用。
字符串拼接、比较
Python 中字符串在进行拼接与比较时,如果一个是str
类型,另一个是unicode
类型,那么会把str
隐式转为unicode
类型。
>>> print "%s, %s" % (u"你好", "中国")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: ''ascii'' codec can''t decode byte 0xe4 in position 0: ordinal not in range(128)
>>>
解决方法也很简单,就像上面说的,只要不涉及到I/O
操作,一律用unicode
类型。
>>> print u"%s, %s" % (u"你好", u"中国")
你好, 中国
读写文件
内置函数 open(name[, mode[, buffering]]) 可以返回一个文件类型的对象,这里返回的文件对象操作的是str
类型的字符,我们可以手动将读到的内容转为unicode
类型,但是这里有个问题, 对于多字节编码来说,一个 unicode 字符可能被数目不同的字节表示,如果我们读取了任意固定大小(比如1K,或4K)的数据块,这个数据快的最后几个字节很可能是某个 unicode 字符的前几个字节,我们需要去处理这种异常,一个比较笨的解决方式是把所有数据读取到内存中,然后再去转码,显然这不适合大数据的情况。一个比较好的方法是使用codecs
模块的 open(filename, mode=''rb'', encoding=None, errors=''strict'', buffering=1)
方法,这个方法返回的文件对象操作的是unicode
类型的字符,
# cat /tmp/debug.log
你好
>>> with open(''/tmp/debug.log'') as f:
>>> s = f.read(1) # 读一个字节
>>> print type(s) # str
>>> print s # 无意义的一个符号
>>>
>>> import codecs
>>>
>>> with codecs.open(''/tmp/debug.log'', encoding=''utf-8'') as f:
>>> s = f.read(1) # 读一个字符
>>> print type(s) # unicode
>>> print s # 你
如果我们用内置的open
进行写文件,必须将unicode
字符转为str
字符,否则会报错。
>>> with open(''/tmp/debug.log'', ''w'') as f:
>>> f.write(u''你好'')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: ''ascii'' codec can''t encode characters in position 0-1: ordinal not in range(128)
这个错误很典型,就是因为用默认的ascii
去编码你好
导致的,显然你好
不在ascii
字符集内,正确的方式:
>>> with open(''/tmp/debug.log'', ''w'') as f:
>>> f.write(u''你好''.encode(''utf-8''))
$ cat /tmp/debug.log
你好
首先需要注意的是 print 在 Python 2 中是一个表达式(和if、return同一级别),而不是一个函数。print
有两种语法形式:
print_stmt ::= "print" ([expression ("," expression)* [","]]
| ">>" expression [("," expression)+ [","]])
默认情况下print
打印到标准输出sys.stdout
中,可以使用>>
后跟一个file-like
的对象(具有write
方法)进行重定向。例如:
with open(''/tmp/debug.log'', ''w'') as f:
print >> f, ''你好''
因为print
的参数为str
类型的字符,所以在打印到标准输出(一般为终端,例如Mac的iTerm2)时有个隐式转码的过程,这个转码过程默认用的编码在类unix系统上是通过环境变量LC_ALL
指定的,在 Windows 系统中,终端默认只能显示256个字符(cp437 指定)。
自 Python 2.6 起,Python 解释器在启动时可以通过指定 PYTHONIOENCODING 这个环境变量来指定。
在程序里面,我们可以通过只读属性sys.stdout.encoding
查看。
$ cat encode.py
# coding: utf-8
import sys
print sys.stdout.encoding
print u"你好"
$ python encode.py
UTF-8
你好
$ LC_ALL=C python encode.py
US-ASCII
Traceback (most recent call last):
File "encode.py", line 21, in <module>
print u"你好"
UnicodeEncodeError: ''ascii'' codec can''t encode characters in position 0-1: ordinal not in range(128)
当 print
通过重定向,不是打印到标准输出sys.stdout
时,由于它不知道目标文件的locale
,所以它又会用默认的ascii
进行编码了。
$ python encode.py > abc
Traceback (most recent call last):
File "encode.py", line 21, in <module>
print u"你好"
UnicodeEncodeError: ''ascii'' codec can''t encode characters in position 0-1: ordinal not in range(128)
$ cat abc
None
$ PYTHONIOENCODING=UTF-8 python encode.py > abc
$ cat abc
UTF-8
你好
可以看到,在不指定PYTHONIOENCODING
时,sys.stdout.encoding
输出None
了,并且执行print u"你好"
时报错了。
为了解决打印unicode字符的问题,我们可以通过codecs.StreamWriter来包装一次sys.stdout
对象。例如:
$ cat encode2.py
# coding: utf-8
import codecs
import sys
UTF8Writer = codecs.getwriter(''utf8'')
sys.stdout = UTF8Writer(sys.stdout)
print u''你好''
$ python encode2.py > abc
$ cat abc
你好
需要注意的是,通过codecs.StreamWriter
包装后的print
,在输出str
类型的字符时,会先把这个字符转为unicode
类型,然后再转为str
类型,这两个转化过程用的也是默认的ascii
编码, 所以很有可能又会出错。
$ cat encode3.py
# coding: utf-8
import codecs
import sys
UTF8Writer = codecs.getwriter(''utf8'')
sys.stdout = UTF8Writer(sys.stdout)
print ''你好''
$ python encode3.py > abc
Traceback (most recent call last):
File "encode3.py", line 7, in <module>
print ''你好''
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py", line 351, in write
data, consumed = self.encode(object, self.errors)
UnicodeDecodeError: ''ascii'' codec can''t decode byte 0xe4 in position 0: ordinal not in range(128)
你可能会问,有没有一劳永逸的解决方法,第三方模块kitchen可以解决这个问题。
$ pip install kitchen
$ cat encode4.py
# coding: utf-8
import sys
from kitchen.text.converters import getwriter
UTF8Writer = getwriter(''utf8'')
sys.stdout = UTF8Writer(sys.stdout)
print u''你好''
print ''你好''
$ python encode4.py > abc
$ cat abc
你好
你好
可以看到,两种类型的你好
均被正确重定向到文件中。
其他
我上面重点讲解了输入输出时的常见编码错误,其他的编码错误基本上就是 API 参数类型不匹配的参数。自己代码推荐还比较好解决,第三方模块里面的就不好调试了,如果遇到了,只能通过hack的方式来修改第三方模块的源代码了。
一个比较好的建议是,str
类型的变量名前面用b_
标示,比如b_search_hits
,表示返回的搜索结果的类型是str
。
never reload(sys)
互联网上比较常见的一个解决编码的方式是:
reload(sys)
sys.setdefaultencoding("utf-8")
这种解决方式带来的弊远远大于利,下面一个简单的例子:
# coding: utf-8
import sys
print "你好" == u"你好"
# False
reload(sys)
sys.setdefaultencoding("utf-8")
print "你好" == u"你好"
# True
可以看到,设置默认编码之后,程序的逻辑已经发生了改变,最主要的是,如果我们改变了默认编码,我们所引用的所有第三方模块,也都会改变,就想我这里举的例子,程序的逻辑很有可能会改变。关于这个问题的详尽解释,可以参考Dangers of sys.setdefaultencoding(''utf-8'')。
总结
通过上面的分析,想象大家对 Python 2 中为什么会出现那么多的编码错误有所了解,根本原因就在于 Python 设计早期混淆了byte
类型与str
类型,好歹在 Python 3 解决了这个设计错误。
在另一方面,这里的编码问题对我们理解计算机的运行原理很有帮助,也反映出copy & paste
的危害,希望大家看了我这篇文章之后,严禁reload(sys)
这种做法。
如果大家对 Python 2 中的编码问题,还有任何疑问,欢迎留言讨论。
参考
Overcoming frustration: Correctly using unicode in python2
PrintFails
Unicode HOWTO
Python 3中的编码
Python 3中的默认编码
python3中默认是UTF-8
可查看python3的默认编码。
>>> import sys >>> >>> sys.getdefaultencoding() 'utf-8' >>>
系统默认编码 指:
在python 3编译器读取.py文件时,若没有头文件编码声明,则默认使用“utf-8”来对.py文件进行解码。并且在调用 encode()这个函数时,不传参的话默认是“ utf-8 ”。(这与下面的open( )函数中的“encoding”参数要做区分)
本地默认编码 指:
在你编写的python 3程序时,若使用了 open( )函数 ,而不给它传入 “ encoding ” 这个参数,那么会自动使用本地 Windows 或 Linux 的默认编码。没错,如果在Windows系统中,就是默认用gbk格式!所以请大家在这里注意:linux中可以不用传“ encoding” 的参数, 而win中不能忘了“ encoding” 的参数)
utf-8是可变长的的编码,有1个字节的英文字符,也有2个字节的阿拉伯文,也有3个字节的中文和日文。utf-8是有严格定义的,一个字节的字符高位必须是0;三个字节的字符中,第一个字节的高位是1110开头。
gbk对英文是使用单字节编码(也就意味着兼容ascii),而gbk对中文部分是采取定长的2字节,总体编码范围为 8140-FEFE,首字节在 81-FE 之间,尾字节在 40-FE 之间。所以说它只要没有碰到尾字节在40之内的字符,都会一股脑地按照2字节去解码成中文。而中文在 utf-8 编码后,一般是三字节的。当解码的字节数和编码的字节数不匹配时,自然会造成全是乱码的局面。
实际上unicode就是一个字符集,一个字符与数字一一对应的映射关系,因为它一律以2个字节编码(或者也有4个字节的,这里不讨论),所以占用空间会大一些,一般只用于内存中的编码使用。
而 utf-8 是为了实现unicode 的传输和存储的。因为它可变长,存英文时候可以节省大量存储空间。传输时候也节省流量。
进程在内存中的表现是“ unicode ”的编码;当python3编译器读取磁盘上的.py文件时,是默认使用“utf-8”的;当进程中出现open(), write() 这样的存储代码时,需要与磁盘进行存储交互时,则是默认使用操作系统的默认编码。
Python 3中的encode和decode
python3中字符编码经常会使用到decode和encode函数。特别是在抓取网页中,这两个函数用的熟练非常有好处。encode的作用,使我们看到的直观的字符转换成计算机内的字节形式。decode刚好相反,把字节形式的字符转换成直观的形式。
>>> "西安".encode() b'\xe8\xa5\xbf\xe5\xae\x89' >>> b'\xe8\xa5\xbf\xe5\xae\x89'.decode() '西安'
\x表示后面是十六进制
在Python 3中, 以字节形式表示的字符串则必须加上前缀b,也就是写成上文的b'xxxx'形式。
UTF-8兼容ASCII
python3中的编码转换
字符以Unicode的字节形式表现出来
>>> "西安".encode('unicode-escape') b'\\u897f\\u5b89' >>> b'\\u897f\\u5b89'.decode('unicode-escape') '西安'
如果我们知道一个Unicode字节码,怎么变成UTF-8的字节码,先decode,再encode。
>>> "西安".encode('unicode-escape') b'\\u897f\\u5b89' >>> "西安".encode('unicode-escape').decode('unicode-escape') '西安' >>> "西安".encode('unicode-escape').decode('unicode-escape').encode() b'\xe8\xa5\xbf\xe5\xae\x89'
源代码文件中,如果有用到非ASCII字符,则需要在文件头部进行字符编码的声明,如下:
#-*- coding: UTF-8 -*-
python3中的字符序列也有两种类型:bytes和str。python3中的bytes和python2中的str相似,str和python2中的unicode相似。这里要注意,str类型在python3和python2中都有,但含义完全变了。
unicode_string=u'中国' print(len(unicode_string)) print( type(unicode_string)) str_string = unicode_string.encode('utf-8') print(len(str_string)) str_string print(type(str_string)) >>> unicode_string=u'中国' >>> print(len(unicode_string)) 2 >>> print( type(unicode_string)) <class 'str'> >>> str_string = unicode_string.encode('utf-8') >>> print(len(str_string)) 6 >>> str_string b'\xe4\xb8\xad\xe5\x9b\xbd' >>> print(type(str_string)) <class 'bytes'>
UnicodeEncoderError
将文本转化为字节序列时,若有字符在目标编码中没有定义,则会出现UnicodeEncoderError。
由于中文字符在ascii编码中无定义,则会报出编码错误。对于此类问题,需选择合适的编码类型,比如含有中文字符,一般用UTF-8编码类型对unicode字符串编码。
>>> unicode_string=u'平国' >>> unicode_string.encode('ascii') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
UnicodeDecodeError
把二进制序列转化为文本时,遇到无法转换的字节序列,则会发生此异常。比如用UTF-8编码后的二进制序列,用GB2312解码,由于两种编码不兼容,用GB2312不能识别字节序列,则会出现异常。
unicode_string=u'中国' utf8_string=unicode_string.encode('utf-8') utf8_string.decode('GB2312')
碰到这种异常,是由于decode使用的编码和字节序列的编码不一致,可以用字符编码侦测包chardet检测字节序列的编码,然后再用此编码解码。
import chardet ### pip install chardet utf8_string=u"中国" str_type=chardet.detect(utf8_string) str_type
#python 3,str和bytes类型相互转换工具类 #file:python3_endecode_helper.py def to_str(bytes_or_str): if isinstance(bytes_or_str,bytes): value = bytes_or_str.decode('UTF-8') else: value = bytes_or_str return value def to_bytes(bytes_or_str): if isinstance(bytes_or_str,str): value = bytes_or_str.encode('UTF-8') else: value = bytes_or_str return value if __name__=='__main__': str_string = u'中国' value = to_bytes(str_string) print(type(value)) #<class 'bytes'> value = to_str(value) print(type(value)) #<class 'str'>
# coding:gbk import sys import locale def p(f): print('%s.%s(): %s' % (f.__module__, f.__name__, f())) # 返回当前系统所使用的默认字符编码 p(sys.getdefaultencoding) # 返回用于转换Unicode文件名至系统文件名所使用的编码 p(sys.getfilesystemencoding) # 获取默认的区域设置并返回元组(语言, 编码) p(locale.getdefaultlocale) # 返回用户设定的文本数据编码 # 文档提到this function only returns a guess p(locale.getpreferredencoding) # 在笔者的Windows上的结果 # sys.getdefaultencoding(): utf-8 # sys.getfilesystemencoding(): utf-8 # locale.getdefaultlocale(): ('en_US', 'cp936') # locale.getpreferredencoding(): cp936
REF
https://www.cnblogs.com/killianxu/p/9746545.html
https://www.cnblogs.com/huxi/archive/2010/12/05/1897271.html
https://blog.csdn.net/ym01213/article/details/89083428
https://zhuanlan.zhihu.com/p/40834093
python urllib中的编码处理示例
>>> import urllib
>>> data = '丽江'
>>> print data
丽江
>>> data
'\xe4\xb8\xbd\xe6\xb1\x9f'
>>> urllib.quote(data)
'%E4%B8%BD%E6%B1%9F'
那我们想转回去呢?
>>> urllib.unquote('%E4%B8%BD%E6%B1%9F')
'\xe4\xb8\xbd\xe6\xb1\x9f'
>>> print urllib.unquote('%E4%B8%BD%E6%B1%9F')
丽江