1 Introduction

参考Pragmatic Unicode

1.1 UCS and CSI

Perl,Python字符串处理: 关于perl, python多字节字符处理方式, 其中:

  • UCS(python): 不管读取的是哪一种编码的字节,程序统一设定为某一种特定编码
  • CSI(Perl): 读取的字节不需要转换,只是把一个字节串加上一个编码的属性

1.2 Five facts of life

关于unicode的5点事实以及3个技巧:

  • Fact of life 1:All input and output of your program is bytes
  • Fact of life 2:The world needs more than 256 symbols to communicate text
  • Fact of life 3:Your program has to deal with both bytes and Unicode
  • Fact of life 4:A stream of bytes can’t tell you its encoding.
  • Fact of life 5:Encoding specifications can be wrong

Three pro tips:

  • Unicode Sandwich: 尽可能确保代码都是unicode, 外层的读写都是bytes
  • Know what you have: 了解你当前处理的子串的编码, 而不是一昧的根据 BUG 来修复
  • Test: 使用测试用例

其中关于life 1和life 2问题, 提出了unicode来进行处理, 其中每一个code point
都用于表示一个特殊的字符(字体).

对于life3, life4: 则需要利用tip1/tip2来尽可能的确保你所处理的所有中文字符都是
在你的预知范围内.

对于life5的解释, 有时候别人告知你的编码可能不是实际的字符编码, 例如对于某一个
HTML页面, 其告知你编码为 UTF8, 但是可笑的是, 实际为 ISO-8859, 此时就需要进行
编码检查( TEST ).

1.3 Code Point

Unicode标准定义了需要将code point表示字节的方法, 他们被称为编码.
注意encodingencode的区别.

注意code point并不等同于byte, 两者的值也是不相等的.

1.4 编码说明

目前已知的编码方案:

  • ASCII 编码: 范围0x00~0xff, 其中一般使用0x00~0x80
  • GBK编码: 汉字内码扩展规范
  • GB2312编码: 汉字编码字符集, 替代 GBK, 长度为 2 字节, 其中: 0xA1A1~0xFEFE 表示汉字信息
  • Unicode编码: 万国码,国际码, 统一码. UCS-4, UCS-2等

其中Unicode编码(常见的 UTF8)是对各个国家MBCS混乱现象的重新编排, 收录各个国家所有文字并
统一.

2 Encode and Decode

2.1 Encode and Decode

  • Encode: 唯一目的, 将字符–>字节, 这才是真正的目的, 其他都是耍流氓
  • Decode: 唯一目的, 将字节–>字符, 这才是真正的目的, 其他都是混日子

2.2 Bytes and Strs

  • Bytes: 一串0和1组成
  • Character: 抽象的, 特定的符号

2.3 Py2 and Py3

  • python2: str–实际为字节串, unicode–实际为字符串
  • python3: bytes–字节串, str–字符串(python2中的unicode)

其中在python2中, 会隐士进行bytes->unicode的转换, 所以在调用print函数的时候,
一些bytes会默认被转为unicode并输出. 对于python3, 不支持隐士转换, 在python3
中, 进行bytes和str的合并操作会抛出异常.

Postscript: 因为py2没有搞清楚字节和字串的含义, 所以导致在py2中对str/unicode的编码,
解码处理常常出现问题, 本来字节串对象当且仅当有函数decode, 字符串当且仅当有encode.

2.4 Example

python2:

1
2
3
4
5
6
7
8
# 首先检测编码类型, 之后才解码
s1 = 'Hi \xe7\xa2\xa7\xe5\xb3\xb0'
import chardet
s1.decode(chardet.detect(s1).get('encoding'))

plain_cn_unicode = u'Hi 碧峰'
p2 = u'Hi \u78a7\u5cf0'
plain_cn_unicode == p2

python3:

1
2
3
4
plain = 'Hello world, 碧峰'
type(plain) == str
plain_b = b'Hello'
type(plain_b) == bytes

3 Python2 Example

3.1 RSA

Function: 进行SHA256加密, 其中SHA256包的输入数据为: 字节码

Process: 构造原始”字符串”—>将原始”字符串”转为”字节串”—>签名返回签名”字节串”—>对签名”字节串”进行base64, 确保数据能够正常传输.

1
2
3
4
5
6
7
# 初始化
key = RSA.importKey(private_key)
signer = PKCS1_v1_5.new(key)
# 将原始字符串变为字节串
sign = signer.sign(SHA256.new(plain.encode('utf-8')))
# 对输出的字节串进行base64
sign_base64 = base64.b64encode(sign)

3.2 Implicit and Encode

对于隐士转换, 以及相关编码解码进行举例, 建议同下一节一起了解

1
2
3
4
5
6
7
# bytes -> unicode
type(u'hello' + '碧峰') == unicode
# 对byte进行解码
'碧峰'.decode('utf8') == u'碧峰'
# 对超出127的byte, 使用 ASCII 进行编码时, 会发现找不到相应的code point来描述
# 该字节串, 报错
assertRaises(Exception, '碧峰'.decode('ascii'))

4 Python3 Example

4.1 Rsa

Function: 按照指定的格式(字节流, 将不同的数据填充的不同byte长度的容器中)进行数据的转换与拼接, 将len(plain)放到一个4字节的字节流中, 将plain转换为bytes

Process: 调用struct转换len(plain)—>调用bytes(plain, encoding=’utf8’)来将”字符串”转变为”字节串”—>拼接各个字节串

Encode Code:

1
2
3
4
5
6
7
# 4字节长度
raw_len_bytes = struct.pack('>I', len(plain))
# 字符串->字节串, 这里可以不同调用bytes, 直接使用: plain.encode('utf8')
plain_bytes = bytes(plain, encoding='utf8')
suite_bytes = bytes(self.suitekey, encoding='utf8')
# 拼接各个字节串
raw_bytes = raw_len_bytes + plain_bytes + suite_bytes

Decode Code

1
2
3
4
# 在进行解密之后(公私钥), 将字节流-->字符串
plain_len = struct.unpack('>I', raw[0:4])
# 字节串->字符串
raw[4:].decode('utf8')

4.2 Encode and Decode

python3不支持隐士转换, 其中编码类似python

1
2
3
4
5
6
7
# 不支持隐士转换, 并且不能转换
assertRaises(Exception, u'hello' + '碧峰')

# 编码
type('碧峰') == str
'碧峰'.encode('utf8') == b'\xe7\xa2\xa7\xe5\xb3\xb0'
# 注意, bytes不可能支持非 ASCII

5 Detech Bytes Encoding

5.1 Introduction

检测bytes的编码类型, 使用chardet模块来完成.

5.2 Example

1
2
3
4
5
# 构建字节流
utfbytes1 = '狂想s1'.encode('utf8')
# 检测
import chardet
chardet.detect(utfbytes1)

6 Json

6.1 utf8

对于包含中文编码的JSON文件, 在进行json->str转换时, 需要使用ensure_ascii来进行转换.

1
2
3
import json
d1 = {'test': '我是中文'}
print json.dumps(d1, ensure_ascii=False)

6.2 save-to-file

对于json, 如果需要以json格式保存到文件中, 可以直接使用dumps方法

1
2
3
4
import json
d1 = {'test': '我是中文'}
with open('/tmp/s1.json', 'w') as f:
json.dumps(d1, indent=4, sort_keys=True)

但是此时, 中文会以Unicode形式存储, 此时需要使用codecs以utf8格式打开文件

1
2
3
4
5
import json
import codecs
d1 = {'test': '我是中文'}
with codecs.open('/tmp/s1.json', 'w', 'utf8') as f:
json.dumps(d1, indent=4, sort_keys=True, ensure_ascii=False)

7 Base64

7.1 Encode

python3中, base64的编码仅仅面向字节字符串或者字节数组, 并且输出结果也总是字节字符串.
如果需要混合字节码和字符串(python3的str就是Unicode), 则需要对原始的str先进行str-->字节码
的操作.

1
2
3
4
import base64
type(u'狂想') == type('狂想')
# 先进行str->bytes, 再进行base64
base64.b64encode('kuang'.encode('utf8'))

python2中, str实际为字节码, 可以直接进行base64编码

1
2
import base64
base64.b64encode('kuang')