如何理解 python UnicodeEncodeError :python 的 string 和 unicode

三月沙 原文链接

文中 python 皆为 2.x 版本

初学 python 的人基本上都有过如下类似经历:

UnicodeDecodeError

1
2
3
Traceback (most recent call last):
File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

UnicodeEncodeError

1
2
3
Traceback (most recent call last):
File "<input>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

这两个错误在 python 中十分常见,一不留神就碰上了。如果你写过c、c++ 或者 java,对比之下一定会觉得 python 这个错误真让人火大。事实也确实如此,我也曾经很火大🔥。

这两个错误究竟意味着什么?可以先从 python 的基本数据类型 string 和 unicode 开始。

string

字符串(string)其实就是一段文本序列,是由一个或多个字符组成(character),字符是文本的最小构成单元,在 python 中可以用以下方式表示字符串:

1
2
3
4
5
6
7
8
9
10
11
12
>>> s1 = 'abc'
>>> s2 = "abc"
>>> s3 = """
abc
"""

>>> s4 = '中文'
>>> for i in [s1, s2, s3, s4]:
print type(i)
<type 'str'>
<type 'str'>
<type 'str'>
<type 'str'>

这些变量在 python shell 中对应输出是:

1
2
3
4
s1 --> 'abc'
s2 --> 'abc'
s3 --> '\nabc\n'
s4 --> '\xe4\xb8\xad\xe6\x96\x87'

s4 的输出和其它变量明显不同,字面上是一个 16 进制序列,但是 s4 和其它字符串一样,在 python 内部都是用同样方式进行存储的: 字节流(byte stream),即字节序列。

字节是计算机内部最小的可寻址的存储单位(对大部分计算机而言),一个字节是由 8 bit 组成,也就是对应 8 个二进制位。其实可以更进一步解释说,python 不仅用字节的方式存储着变量中的字符串文本,python 文件中的所有信息在计算机内部都是用一个个字节表示的,计算机是用这样的方式存储文本数据的。

字符串用字节如何表示?

答案就是编码。计算机是只能识别 0 或 1 这样的二进制信息,而不是 a 或 b 这样对人类有意义的字符,为了让机器能读懂这些字符,人类就发明字符到二进制的映射关系,然后按照这个映射规则进行相应地编码。ascii 就是这样背景下诞生的一种编码规则。ascii 也是 python 2.x 默认使用的编码规则。

ascii 规定了常用的字符到计算机是如何映射的,编码范围是 0~127 共 128 个字符。简单来说它就是一本字典,规定了不同字符的对应的编码值(code point,一个整数值),这样一来计算机就能用二进制表示了。比如字符 a 的编码是 97,对应的二进制是 1100001,一个字节就足够存储这些信息。字符串 “abc” 最终存储就是 [97] [98] [99] 三个字节。python 默认情况下就是使用这个规则对字符进行编码,对字节进行解码(反编码)。

1
2
3
4
5
>>> ord('a')
97
>>> chr(97)
'a'
>>>

由于 ascii 的编码范围非常有限,对超过 ascii 范围之外的字符,python 是如何处理的?很简单,抛错误出来,这就是 UnicodeEncodeErrorUnicodeDecodeError 的来源。那 python 会在什么时候抛出这样的错误,也就是说 python 进行编码和解码的操作发生在何时?

unicode 对象

unicode 对象和 string 一样,是 python 中的一种字符对象(python 中一切皆对象,string 也是)。先不要去想 unicode 字符集、unicode 编码或者 utf-8 这些概念,在此特意加了对象就是为了和后面提到的 unicode 字符集进行区分。这里说的 unicode 就是 python 中的 unicode 对象,构造函数是 unicode()

在 python 中创造 unicode 对象也很简单:

1
2
3
4
>>> s1 = unicode('abc')
>>> s2 = u'abc'
>>> s3 = U'abc'
>>> s4 = u'中文'

这些变量在 python shell 中对应输出是:

1
2
3
4
s1 --> u'abc'
s2 --> u'abc'
s3 --> u'abc'
s4 --> u'\u4e2d\u6587'

同样的,s4 的输出和其它变量不同,这些就是unicode 字符。由于 ascii 能够表示的字符太少,而且不够通用(扩展 ascii 的话题,就是把 ascii 没有利用的剩下大于 127 的位置利用了,在不同的字符集里代表不同的意思),unicode 字符集 就被造出来了,一本更大的字典,里面有更多的编码值。

unicode 字符集

unicode 字符集解决了:

  • ascii 表达能力不够的问题
  • 扩展 ascii 不够通用的问题

虽然 unicode 字符集表达能力强,又能够统一字符编码规则,但是它并没有规定这些字符在计算机中是如何表示的。它和 ascii 不同,很多字符(编码值大于 255 )没有办法用一个字节就搞定。怎样做到高效快捷地存储这些编码值?于是就有了 unicode 字符集的编码规则的实现:utf-8、utf-16等。

到这里可以简单理清 ascii、unicode 字符集、utf-8等的关系了:ascii 和 unicode 字符集都是一种编码集,由字符和字符对应的整数值(code point)组成,ascii 在计算机内部用一个字节存储,utf-8 是 unicode 字符集存储的具体实现,因为 unicode 字符集没有办法简简单单用一个字节搞定。

回到 s4 对应的输出,这个输出就是 unicode 字符集对应的编码值(code point)的 16 进制表示。

unicode 对象是用来表示 unicode 字符集中的字符,这些字符(实际是那个编码值,一个整数) 在 python 中又是如何存储的?有了前文的分析,也许可以猜到,python 依然是通过编码然后用字节的方式存储,但是这里的编码就不能是 ascii 了,而是对应 unicode 字符集的编码规则: utf-8、utf-16等。

unicode 对象的编码

unicode 对象想要正确的存储就必须指定相应的编码规则,这里我们只讨论使用最广泛的 utf-8 实现。

在 python 中对 unicode 对象编码如下:

1
2
3
4
5
>>> s=u'中文'
>>> s.encode('utf-8')
'\xe4\xb8\xad\xe6\x96\x87'
>>> type(s.encode('utf-8'))
<type 'str'>

编码之后输出的是个 string 并以字节序列的方式进行存储。有了编码就会有解码,python 正是在这种编码、解码的过程使用了错误的编码规则而发生了 UnicodeEncodeErrorUnicodeDecodeError 错误,因为它默认使用 ascii 来完成转换。

string 和 unicode 对象的转换

unicode 对象可以用 utf-8 编码为 string,同理,string 也可以用 utf-8 解码为 unicode 对象

1
2
3
4
5
6
7
8
9
10
>>> u=u'中文'
>>> s = u.encode('utf-8')
>>> s
'\xe4\xb8\xad\xe6\x96\x87'
>>> type(s)
<type 'str'>
>>> s.decode('utf-8')
u'\u4e2d\u6587'
>>> type(s.decode('utf-8'))
<type 'unicode'>

错误的编码规则就会导致那两个常见的异常

1
2
3
4
5
6
7
8
9
>>> u.encode('ascii')
Traceback (most recent call last):
File "<input>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>>
>>> s.decode('ascii')
Traceback (most recent call last):
File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

这两个错误在某些时候会突然莫名其妙地出现就是因为 python 自动地使用了 ascii 编码。

python 自动解编码

1.stirng 和 unicode 对象合并

1
2
3
4
5
>>> s + u''
Traceback (most recent call last):
File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
>>>

2.列表合并

1
2
3
4
5
>>> as_list = [u, s]
>>> ''.join(as_list)
Traceback (most recent call last):
File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

3.格式化字符串

1
2
3
4
5
>>> '%s-%s'%(s,u)
Traceback (most recent call last):
File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
>>>

4.打印 unicode 对象

1
2
3
4
5
6
7
8
9
10
#test.py
# -*- coding: utf-8 -*-
u = u'中文'
print u

#outpt
Traceback (most recent call last):
File "/Users/zhyq0826/workspace/zhyq0826/blog-code/p20161030_python_encoding/uni.py", line 3, in <module>
print u
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

5.输出到文件

1
2
3
4
5
6
>>> f = open('text.txt','w')
>>> f.write(u)
Traceback (most recent call last):
File "<input>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>>

1,2,3 的例子中,python 自动用 ascii 把 string 解码为 unicode 对象然后再进行相应操作,所以都是 decode 错误, 4 和 5 python 自动用 ascii 把 unicode 对象编码为字符串然后输出,所以都是 encode 错误。

只要涉及到 unicode 对象和 string 的转换以及 unicode 对象输出、输入的地方可能都会触发 python 自动进行解码/编码,比如写入数据库、写入到文件、读取 socket 等等。

到此,这两个异常产生的真正原因了基本已经清楚了: unicode 对象需要编码为相应的 string(字符串)才可以存储、传输、打印,字符串需要解码为对应的 unicode 对象才能完成 unicode 对象的各种操作,lenfind 等。

1
2
string.decode('utf-8') --> unicode
unicode.encode('utf-8') --> string

如何避免这些的错误

1.理解编码或解码的转换方向

无论何时发生编码错误,首先要理解编码方向,然后再针对性解决。

2.设置默认编码为 utf-8

在文件头写入

1
# -*- coding: utf-8 -*-

python 会查找: coding: name or coding=name,并设置文件编码格式为 name,此方式是告诉 python 默认编码不再是 ascii ,而是要使用声明的编码格式。

3.输入对象尽早解码为 unicode,输出对象尽早编码为字节流

无论何时有字节流输入,都需要尽早解码为 unicode 对象。任何时候想要把 unicode 对象写入到文件、数据库、socket 等外界程序,都需要进行编码。

4.使用 codecs 模块来处理输入输出 unicode 对象

codecs 模块可以自动的完成解编码的工作。

1
2
3
4
>>> import codecs
>>> f = codecs.open('text.txt', 'w', 'utf-8')
>>> f.write(u)
>>> f.close()

参考文献

注意:转载请注明出处和文章链接

记一次 mongodb 单表亿级数据的拆分方案

三月沙 原文链接

拆表是一种常见的解决单表数据库瓶颈的方案,在实际的应用场景中能够部分解决单表的写压力和读压力,但是也会带来一些更复杂的影响:

  • 聚合查询变得困难
  • 拆分的键一旦选定,更改会非常困难
  • 拆表的过程要保证线上业务不受影响,操作复杂度高

因此,表的拆分一定要选在恰当的时候进行,过早,付出很大代价也并不会带来性能的提升,过晚,数据量庞大,操作难度加大。

在本次实施的拆分方案中,数据特点是:

  • 单表过亿
  • 业务数据是用户对资源的收藏,结构比较单一
  • 只做单向(用户–>数据)查找,不要求反向(数据–>用户)查找
  • 数据处于实时变更状态(新增、删除、查询等操作并存)

如何选择拆分键

本例中,拆分的键就是用户 id,且每个用户关联的资源数据最大不会过万。

如何保证拆分的过程中不影响用户的操作?

本例中,表中的数据基本上只有如下操作

  • 用户新增一条数据
  • 用户删除一条数据
  • 用户查询数据(有翻页)

如何用 redis 造一把分布式锁

三月沙 原文链接

基本概念

wiki:In computer science, a lock or mutex (from mutual exclusion) is a synchronization mechanism for enforcing limits on access to a resource in an environment where there are many threads of execution. A lock is designed to enforce a mutual exclusion concurrency control policy.
在计算机科学中,锁或互斥量是一种同步机制,用于在多线程执行环境中,强行限制对资源访问。锁常被用于同步并发控制。

简单来讲,锁是用来控制多线程执行对资源的并发访问的。比如当一个资源只允许在任意时刻只有一个执行线程对其进行写操作,那当其他线程要访问资源时,就必须要检查该该资源上是否存在写操作锁,如果存在,必须要等待锁的释放并获得锁之后才能对资源进行访问。

悲观锁

悲观锁假设在一个完整事务发生的过程中,总是会有其他线程会更改所操作的资源,因此线程总是对资源加锁之后才会对其做更改。

乐观锁

乐观锁假设在一个完整事务发生的过程中,不一定会有其他线程会更改资源,一旦发现资源被更改,则停止当前事务回滚所有操作。

分布式锁

常见的锁有多线程锁、数据库事务中的行级锁、表级锁等,这些锁的特点都是发生在单一系统环境中,如果需要在不同的进程、不同机器和系统等分布式环境中控制对资源的访问,这时候我们需要一把分布式锁。

不言而喻,分布式锁就是为了解决分布式环境中对资源的访问限制而诞生的。

如何设计一把分布式锁

我为什么创造了 turbo 这个后端的轮子

三月沙 原文链接

turbo 与其说是一个 framework ,不如说是一个后端的解决方案。

turbo 是如何诞生的?

tornado 是异步非阻塞的 web 服务器,同时也是一个 web framework,功能简单但完全够用。

原东家的技术栈是:tornado、mongodb、redis、openresty,最大的服务每天服务的独立用户有上百万。早期大部分项目完全使用 tornado 最原始的方式构建,一个 main 文件包含几百个路由,所有的 handler 分布在十几个大大小小的文件夹下面,项目基本的文件结构包含:

  • 一个抽象的 dal 层(数据抽象)
  • 数据库连接(db)
  • 配置(conf)
  • 工具(utils)
  • 缓存(cache)
  • 第三方库(lib)

随着项目的代码越来越多,多个特性开发常常同时进行,开发人员经常要非常小心地应对代码的更改,即使如此,冲突依然时常发生,代码的可维护性和健壮性日益变差。

创业公司由于业务面临飞速增长,多条业务线齐头并进,开发团队往往要同时维护很多个项目,有的项目生命周期很短,一次活动就结束了,有的项目要对外合作,随合作的结束而结束,有的项目服务新的产品,需要快速构建。不同的项目由不同的开发人员创建,创建项目的方式基本是 copy 旧项目,比较原始,容易出错。项目结构在不同的项目之间的差异随着业务的不同,差别渐渐凸显,重复的功能性代码和工具性代码也逐渐变多。

How about a nice cup of React

落在深海 原文链接

接触 React 一年左右,期间在单页面、单页应用都有使用, 它极大地改变了我个人的前端开发方式。为方便快速出产品,闲暇之余也尝试写了点基础组件: react-component, react-image-cropperreact-touch-gallery, react-file-upload。也一直计划着写点心得总结什么的,迫于忙、浮躁,难静下心写东西,屡次作罢。这两天刚离职,决定把这篇总结补上。

你真得理解 python 的浅拷贝和深拷贝吗?

三月沙 原文链接

为了让一个对象发生改变时不对原对象产生副作用,此时,需要一份这个对象的拷贝,python 提供了 copy 机制来完成这样的任务,对应的模块是 copy

浅拷贝:shadow copy

copy 模块中,有 copy 函数可以完成浅拷贝。

1
from copy import copy

在 python 中,标识一个对象唯一身份的是:对象的id(内存地址),对象类型,对象值,而浅拷贝就是创建一个具有相同类型,相同值但不同id的新对象。

对可变对象而言,对象的值一样可能包含有对其他对象的引用,浅拷贝产生的新对象,虽然具有完全不同的id,但是其值若包含可变对象,这些对象和原始对象中的值包含同样的引用。

1
2
3
4
5
6
7
8
9
10
11
12
>>> import copy
>>> l = {'a': [1,2,3], 'b':[4,5,6]}
>>> c = copy.copy(l)
>>> id(l) == id(c)
False
>>> l['a'].append('4')
>>> c['b'].append('7')
>>> l
{'a': [1, 2, 3, '4'], 'b': [4, 5, 6, '7']}
>>> c
{'a': [1, 2, 3, '4'], 'b': [4, 5, 6, '7']}
>>>

可见浅拷贝产生的新对象中可变对象的值在发生改变时会对原对象的值产生副作用,因为这些值是同一个引用。

为什么选择 emberjs 开发 dashboard 和 CMS 类系统

三月沙 原文链接

emberjs 是什么

在开始之前,先看一组 github 数据

ember.js

  • star 16291
  • contributors 586
  • releases 181
  • issues 167 open, 4397 closed

https://github.com/emberjs/ember.js

angular.js

  • star 49643
  • contributors 1476
  • releases 169
  • issues 817 open, 6992 closed

https://github.com/angular/angular.js

vue.js

  • star 19883
  • contributors 69
  • releases 137
  • issues 27 open, 2252 closed

https://github.com/vuejs/vue

react.js

  • star 43026
  • contributors 713
  • releases 46
  • issues 480 open, 2843 closed

https://github.com/facebook/react

riot.js

  • star 9565
  • contributors 138
  • releases 137
  • issues 96 open, 1089 closed

https://github.com/riot/riot

注: 统计时间为本文编写之时

emberjs 和其他同类框架一样,至少在 github 的 表现足以证明其流行度是不低的,但各个框架的诞生的场景以及解决的问题又不尽相同。

emberjs 是基于 jquery 的一个全栈式前端框架,它提供了从路由层-> view 层->数据和网络交互层的全部解决方案,只用 emberjs 就足以解决前端单页应用遇到的所有复杂问题,而且兼具有不俗的开发效率和开发质量。

全栈式解决方案

emberjs 是一个大而全的框架,它几乎囊括了单页应用开发所需要的方方面面: component,model,service,route,temlate
,ember-data。借助 emberjs 几乎可以完成任何规模的复杂应用,而不用担心它没有提供足够丰富的特性或者需要求助其他第三方库(不一定可以很好的和框架本身集成)。

python的模块加载和路径查找

三月沙 原文链接

深入这个问题之前,我们需要理解几个概念:

module:模块, 一个 py文件或以其他文件形式存在的可被导入的就是一个模块

package:包,包含有 __init__ 文件的文件夹

relative path:相对路径,相对于某个目录的路径

absolute path:绝对路径,全路径

路径查找:python 解释器查找被引入的包或模块

python 执行时是如何查找包和模块的

1.python 执行一个文件

python 执行一个文件,无论执行的方式是绝对路径还是相对路径,解释器都会把文件所在的目录加入到系统查找路径中,也就是 sys.path 这个 list 中,而 sys.path 又是由系统的 python 环境变量决定的。

wecatch 官方博客开放啦!

wecatch,独立自由的组织,致力于构建更好的Web产品, 并为开发者和团队提供更敏捷,稳定,灵活的Web解决方案以及专业的咨询服务

本博客致力于分享程序技术,科技生活相关内容,欢迎关注。