#13 Python UCS 相关问题

2023-12-08

老项目中有时可以看到报错:unichr() arg not in range(0x10000) (narrow Python build)
经过一番调研之后,发现是和 Python 内部 Unicode 编码有关。

#12 GSM-7 编码

2023-01-05

https://en.wikipedia.org/wiki/GSM_03.38

GSM 引入短信服务的时候,规范了相关的字符编码。

其中,定义了一个 7bit 的字符编码,支持英语和一些西欧语言,这套标准就是 GSM 03.38,也被称之为 3GPP 23.038,在移动通信技术领域之外,更广泛的称呼是 GSM-7 编码(gsm7bit)。

  • PS:短信中如果要包含其他语言,必须使用 16 位 UCS-2 字符编码进行传输。
  • PS:按照 GSM 03.40 标准(TP-Data-Coding-Scheme),一共支持三种编码(00 GSM7bit,01 8bit,02 UCS2)
  • PS:3GPP 23.038 中还又定义一个 national language shift table,
    可以通过相关代码在 GSM-7 的技术框架内支持一些语言,比如葡萄牙语、西班牙语、土耳其语等。
    中文这种字符这么多的就别想了。

字符集

分析

  1. 算是可变长度编码,拓展区域字符需要用 ESC (0x1B) 开头,算两位长度。

  2. ASCII 可打印字符中(Python string.printable),有四个不支持:

`
\t      # TAB   水平制表符
\x0b    # VT    垂直制表符
\x0c    # FF    换页
  1. GSM-7 基本兼容同为七位编码的 ASCII:

  2. ASCII 可打印字符中,有 8 个符号放在拓展区,

  3. 其他的和 GSM-7 编码完全相同。
^ ~ \ | { } [ ]
  1. 图中标黄色的 7 个字符说明:

  2. BCS:

    1. LF \r 换行
    2. CR \n 回车
    3. ESC
    4. SP
  3. EXT:

    1. FF 1B0A 对应 ASCII 0C 位置的换页符
    2. CR2 1B0D
    3. SS2 1B1B 保留
  4. GSM-7 用控制字符换了以下 40 个字符:

'£', '¥', 'è', 'é', 'ù', 'ì', 'ò', 'Ç', 'Ø', 'ø',
'Å', 'å', 'Δ', 'Φ', 'Γ', 'Λ', 'Ω', 'Π', 'Ψ', 'Σ',
'Θ', 'Ξ', 'Æ', 'æ', 'ß', 'É', '¤', '¡', 'Ä', 'Ö',
'Ñ', 'Ü', '§', '¿', 'ä', 'ö', 'ñ', 'ü', 'à', '€',

对短信长度计算的影响

和 8bit 的 ASCII 编码方案相比:

  1. 利用那些没有用的控制位,支持更多语言(同时,牺牲了 8 个标点符号,需要两个字节表示)
  2. 140 * 8 / 7 = 160,也就是一条短信支持的字符数,从 140 提升到 160。

如果超出就按照 153 个字符切割,比如:

500 个 GSM-7 字符的短信,在通信过程中需要切割成 153 + 153 + 153 + 41 四条短信发出。
每条短信带一个头(附加信息),包含这批短信的总条数和当前这条短信的序号。
收信方根据短信的头信息来把这一批短信组装成一条,展示给用户。

参考:2018/06/08,短信长度到底是怎么规定的

填充位

根据协议:
如果剩余长度 1 ~ 6,最后的几位应该用 0 填充。
如果剩余长度位 7,为了避免和 0x00 @ 混淆,应该用 0x0D 填充,也就是 \r(回车)。

stat = {}

for i in range(1, 161):
    stat.setdefault(8 - (i * 7 % 8), []).append(i)

pprint.pp(stat, width=160)
{1: [1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, 97, 105, 113, 121, 129, 137, 145, 153],
 2: [2, 10, 18, 26, 34, 42, 50, 58, 66, 74, 82, 90, 98, 106, 114, 122, 130, 138, 146, 154],
 3: [3, 11, 19, 27, 35, 43, 51, 59, 67, 75, 83, 91, 99, 107, 115, 123, 131, 139, 147, 155],
 4: [4, 12, 20, 28, 36, 44, 52, 60, 68, 76, 84, 92, 100, 108, 116, 124, 132, 140, 148, 156],
 5: [5, 13, 21, 29, 37, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, 133, 141, 149, 157],
 6: [6, 14, 22, 30, 38, 46, 54, 62, 70, 78, 86, 94, 102, 110, 118, 126, 134, 142, 150, 158],
 7: [7, 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95, 103, 111, 119, 127, 135, 143, 151, 159],
 8: [8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160]}

参考资料与拓展阅读

#11 一个编码转换问题

2022-01-15

问题

我拿到一串字符串:

"Client: \\344\\275\\240\\345\\245\\275\\357\\274\\214\\344\\270\\226\\347\\225\\214"

根据上下文,我知道这是 UTF-8。

print(b'Client: \344\275\240\345\245\275\357\274\214\344\270\226\347\225\214'.decode())
# 'Client: 你好,世界'

现在的问题是,我怎么把他转换成普通的字符串呢(unicode)?

再举例子

这其实是一个比较普遍的问题,只要有过一段时间 Python 开发,可能就会偶尔碰到这样的问题:

a = "I Love 中国".encode('utf-8')
b = str(a)
# "b'I Love \\xe4\\xb8\\xad\\xe5\\x9b\\xbd'"

由于不同系统或者不同模块之间的衔接有问题(有可能部分组件只支持 ASCII),中间的过程不受我们控制,最后,我们就是拿到了这个 b,怎么给转换成 a?

解决方案

s = "Client: \\344\\275\\240\\345\\245\\275\\357\\274\\214\\344\\270\\226\\347\\225\\214"
result = eval(f'b"{s}".decode("utf-8")')
print(result)
# Client: 你好,世界

但是这有点危险,改成 str.literal_eval 好一些:

s = "Client: \\344\\275\\240\\345\\245\\275\\357\\274\\214\\344\\270\\226\\347\\225\\214"
#  b'Client: \xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c'
import ast
print(ast.literal_eval(f'b"{s}"').decode('utf-8'))

延伸

unicode_escape

In [1]: u'nihao 中国'.encode('raw_unicode_escape')
Out[1]: 'nihao \\u4e2d\\u56fd'

In [2]: u'nihao 中国'.encode('unicode_escape')
Out[2]: 'nihao \\u4e2d\\u56fd'

In [3]: re.sub('.', lambda x: r'\u%04X' % ord(x.group()), 'nihao 中国')
Out[3]: '\\u006E\\u0069\\u0068\\u0061\\u006F\\u0020\\u4E2D\\u56FD'

In [4]: ''.join(r'\u{:04X}'.format(ord(chr)) for chr in 'nihao 中国')
Out[4]: '\\u006E\\u0069\\u0068\\u0061\\u006F\\u0020\\u4E2D\\u56FD'

八进制和十六进制

s = "Client: \\344\\275\\240\\345\\245\\275\\357\\274\\214\\344\\270\\226\\347\\225\\214"
#  b'Client: \xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c'
0o344 == 0xe4 == 228

#10 BOM 头的研究

2020-11-03

BOM 是 Byte Order Mark 的缩写,代表一个 Unicode 字符 FEFF
Windows 系统下的很多软件就用 BOM 字符作为 Magic Number, 用来确认文件的字符编码和字节顺序。

#9 字符编码

2020-11-01

从原理上来讲,我们的计算机其实只认识数字(要不然为什么叫做计算机),确切的说是 0 和 1,我们的文字信息存放在计算机中也是以数字形式存在。
所谓字符编码就是字符和数字之间的对应关系和转换规则。

#8 “锟斤拷” 到底是个啥?

2020-11-01

img

手持两把锟斤拷,
口中疾呼烫烫烫。
脚踏千朵屯屯屯,
笑看万物锘锘锘。

开发者经常这样调侃编码问题:手持两把锟斤拷,口中疾呼烫烫烫。

程序员都知道字符编码,比如 ASCII,GBK,UTF-8,那肯定能理解这些神奇的 “乱码” 出现的原因,肯定是某些地方没有正确处理编码。

那为什么总是能看见 锟斤拷烫烫烫呢?其背后隐藏更深的原因是啥?

#7 JavaScript Unicode 问题

2019-06-20
function base64EncodeUnicode(str) {
  // First we escape the string using encodeURIComponent to get the UTF-8 encoding of the characters,
  // then we convert the percent encodings into raw bytes, and finally feed it to btoa() function.
  utf8Bytes = encodeURIComponent(str).replace(
    /%([0-9A-F]{2})/g,
    function (match, p1) {
      return String.fromCharCode("0x" + p1);
    },
  );

  return btoa(utf8Bytes);
}
x.decode('utf-8').encode('raw_unicode_escape').decode('ascii')[2:].strip('0')

#6 ASCII 码表

2017-04-15
Bin Oct Dec Hex 缩写/字符 解释
0000 0000 000 0 00 NUL(null) 空字符
0000 0001 001 1 01 SOH(start of headline) 标题开始
0000 0010 002 2 02 STX (start of text) 正文开始
0000 0011 003 3 03 ETX (end of text) 正文结束
0000 0100 004 4 04 EOT (end of transmission) 传输结束
0000 0101 005 5 05 ENQ (enquiry) 请求
0000 0110 006 6 06 ACK (acknowledge) 收到通知
0000 0111 007 7 07 BEL (bell) 响铃
0000 1000 010 8 08 BS (backspace) 退格
0000 1001 011 9 09 HT (horizontal tab) 水平制表符
0000 1010 012 10 0A LF (NL line feed, new line) 换行键
0000 1011 013 11 0B VT (vertical tab) 垂直制表符
0000 1100 014 12 0C FF (NP form feed, new page) 换页键
0000 1101 015 13 0D CR (carriage return) 回车键
0000 1110 016 14 0E SO (shift out) 不用切换
0000 1111 017 15 0F SI (shift in) 启用切换
0001 0000 020 16 10 DLE (data link escape) 数据链路转义
0001 0001 021 17 11 DC1 (device control 1) 设备控制 1
0001 0010 022 18 12 DC2 (device control 2) 设备控制 2
0001 0011 023 19 13 DC3 (device control 3) 设备控制 3
0001 0100 024 20 14 DC4 (device control 4) 设备控制 4
0001 0101 025 21 15 NAK (negative acknowledge) 拒绝接收
0001 0110 026 22 16 SYN (synchronous idle) 同步空闲
0001 0111 027 23 17 ETB (end of trans. block) 结束传输块
0001 1000 030 24 18 CAN (cancel) 取消
0001 1001 031 25 19 EM (end of medium) 媒介结束
0001 1010 032 26 1A SUB (substitute) 代替
0001 1011 033 27 1B ESC (escape) 换码(溢出)
0001 1100 034 28 1C FS (file separator) 文件分隔符
0001 1101 035 29 1D GS (group separator) 分组符
0001 1110 036 30 1E RS (record separator) 记录分隔符
0001 1111 037 31 1F US (unit separator) 单元分隔符
0010 0000 040 32 20 (space) 空格
0010 0001 041 33 21 ! 叹号
0010 0010 042 34 22 " 双引号
0010 0011 043 35 23 # 井号
0010 0100 044 36 24 $ 美元符
0010 0101 045 37 25 % 百分号
0010 0110 046 38 26 & 和号
0010 0111 047 39 27 ' 闭单引号
0010 1000 050 40 28 ( 开括号
0010 1001 051 41 29 ) 闭括号
0010 1010 052 42 2A * 星号
0010 1011 053 43 2B + 加号
0010 1100 054 44 2C , 逗号
0010 1101 055 45 2D - 减号/破折号
0010 1110 056 46 2E . 句号
0010 1111 057 47 2F / 斜杠
0011 0000 060 48 30 0 数字 0
0011 0001 061 49 31 1 数字 1
0011 0010 062 50 32 2 数字 2
0011 0011 063 51 33 3 数字 3
0011 0100 064 52 34 4 数字 4
0011 0101 065 53 35 5 数字 5
0011 0110 066 54 36 6 数字 6
0011 0111 067 55 37 7 数字 7
0011 1000 070 56 38 8 数字 8
0011 1001 071 57 39 9 数字 9
0011 1010 072 58 3A : 冒号
0011 1011 073 59 3B ; 分号
0011 1100 074 60 3C < 小于
0011 1101 075 61 3D = 等号
0011 1110 076 62 3E > 大于
0011 1111 077 63 3F ? 问号
0100 0000 100 64 40 @ 电子邮件符号
0100 0001 101 65 41 A 大写字母 A
0100 0010 102 66 42 B 大写字母 B
0100 0011 103 67 43 C 大写字母 C
0100 0100 104 68 44 D 大写字母 D
0100 0101 105 69 45 E 大写字母 E
0100 0110 106 70 46 F 大写字母 F
0100 0111 107 71 47 G 大写字母 G
0100 1000 110 72 48 H 大写字母 H
0100 1001 111 73 49 I 大写字母 I
0100 1010 112 74 4A J 大写字母 J
0100 1011 113 75 4B K 大写字母 K
0100 1100 114 76 4C L 大写字母 L
0100 1101 115 77 4D M 大写字母 M
0100 1110 116 78 4E N 大写字母 N
0100 1111 117 79 4F O 大写字母 O
0101 0000 120 80 50 P 大写字母 P
0101 0001 121 81 51 Q 大写字母 Q
0101 0010 122 82 52 R 大写字母 R
0101 0011 123 83 53 S 大写字母 S
0101 0100 124 84 54 T 大写字母 T
0101 0101 125 85 55 U 大写字母 U
0101 0110 126 86 56 V 大写字母 V
0101 0111 127 87 57 W 大写字母 W
0101 1000 130 88 58 X 大写字母 X
0101 1001 131 89 59 Y 大写字母 Y
0101 1010 132 90 5A Z 大写字母 Z
0101 1011 133 91 5B [ 开方括号
0101 1100 134 92 5C \ 反斜杠
0101 1101 135 93 5D ] 闭方括号
0101 1110 136 94 5E ^ 脱字符
0101 1111 137 95 5F _ 下划线
0110 0000 140 96 60 ` 开单引号
0110 0001 141 97 61 a 小写字母 a
0110 0010 142 98 62 b 小写字母 b
0110 0011 143 99 63 c 小写字母 c
0110 0100 144 100 64 d 小写字母 d
0110 0101 145 101 65 e 小写字母 e
0110 0110 146 102 66 f 小写字母 f
0110 0111 147 103 67 g 小写字母 g
0110 1000 150 104 68 h 小写字母 h
0110 1001 151 105 69 i 小写字母 i
0110 1010 152 106 6A j 小写字母 j
0110 1011 153 107 6B k 小写字母 k
0110 1100 154 108 6C l 小写字母 l
0110 1101 155 109 6D m 小写字母 m
0110 1110 156 110 6E n 小写字母 n
0110 1111 157 111 6F o 小写字母 o
0111 0000 160 112 70 p 小写字母 p
0111 0001 161 113 71 q 小写字母 q
0111 0010 162 114 72 r 小写字母 r
0111 0011 163 115 73 s 小写字母 s
0111 0100 164 116 74 t 小写字母 t
0111 0101 165 117 75 u 小写字母 u
0111 0110 166 118 76 v 小写字母 v
0111 0111 167 119 77 w 小写字母 w
0111 1000 170 120 78 x 小写字母 x
0111 1001 171 121 79 y 小写字母 y
0111 1010 172 122 7A z 小写字母 z
0111 1011 173 123 7B { 开花括号
0111 1100 174 124 7C | 垂线
0111 1101 175 125 7D } 闭花括号
0111 1110 176 126 7E ~ 波浪号
0111 1111 177 127 7F DEL (delete) 删除