我去,原来如此复杂。
感谢楼上几位给出的资料和方法。
python 源码中这个文件有 15665 行,实在是让人望而生畏。
@
ruoyu0088 提到编码是自动选择的,我结合读文档的理解和一些测试,
再次进行了一遍求证,最后得到的结论是:
python3 的字符串是根据输入确定编码,在 Latin1 , UTF16 、 UTF32 之间进行切换。
验证的过程是这样的:
1. 首先是 ucs2(utf16) 的情况 (据我所知 ucs2 与 u16 等价, ucs4 与 u32 等价,不知是否正确)
In [60]: a = '中文'
In [61]: binascii.hexlify(ctypes.string_at(id(a), a.__sizeof__()))
Out[61]: b'010000003094e51d0200000056f1a772a8000000f447c7000000000000000000020000002d4e87650000'
In [62]: binascii.hexlify('中文'.encode('utf-16'))
Out[62]: b'fffe2d4e8765'
我们可以看到,“中文”两个字在编码为 utf16 之后首先是 fffe 这个头部,随后的 2d4e 8765 分别对应两个字,这与从内存中弄到的字符串形态是相同的。
2. 那么我们在文本中加入一个 UCS2 表示不了的字符串呢?会怎么样?
In [64]: a = '\U000a1ffa 中文'
In [65]: binascii.hexlify(ctypes.string_at(id(a), a.__sizeof__()))
Out[65]: b'010000003094e51d03000000220bc0a8b000000000000000000000000000000000000000fa1f0a002d4e00008765000000000000'
我们可以看到, 2d4e 变成了 2d4e0000 , 8765 变成了 87650000 ,最后是 8 个 0 (一个 UCS4 字符)结尾。
而 fa1f0a00 是 000a1ffa 在内存中的形式(从右向左,每一个字节——即俩 HEX ——逐个倒装)
其实我觉得奇怪的地方在于, python 其实记录了文本的长度,为啥坚持 C 风格的字符串(末尾加\0 )?
看看这个字符串 encode 后的样子吧!
In [66]: binascii.hexlify('\U000a1ffa 中文'.encode('utf-32'))
Out[66]: b'fffe0000fa1f0a002d4e000087650000'
In [67]: binascii.hexlify('\U000a1ffa 中文'.encode('utf-8'))
Out[67]: b'f2a1bfbae4b8ade69687'
头部变成了 fffe0000 其他都一致。
3. 最后再看看单字节的字符串
In [68]: a = "\x9a\x9b"
In [69]: binascii.hexlify(ctypes.string_at(id(a), a.__sizeof__()))
Out[69]: b'010000003094e51d02000000b9bdd189a4000000000000000000000000000000000000009a9b00'
=====
In [70]: a = "\x9a\x9b 中"
In [71]: binascii.hexlify(ctypes.string_at(id(a), a.__sizeof__()))
Out[71]: b'010000003094e51d03000000a4036e4ba8656164f44dc7000000000000000000030000009a009b002d4e0000'
=====
In [72]: a = "\x9a\x9b 中\U000a1ffa"
In [73]: binascii.hexlify(ctypes.string_at(id(a), a.__sizeof__()))
Out[73]: b'010000003094e51d04000000776e9375b000005a000000000000000000000000000000009a0000009b0000002d4e0000fa1f0a0000000000'
果然不出所料。
说起来\U0000000 是一个比较少用的语法,专门用来转义 UCS4 的。
要不是前段时间搞了 tinyre 这个项目,我肯定是弄不出这样的字符的……
顺便宣传一下在下最近这个项目,一个正则引擎:
https://github.com/fy0/tinyre