找出 CPython 用于特定 unicode 字符串的确切内部编码的一种方法是查看实际 (CPython) 对象。
根据PEP 393(Specification部分),所有的unicode字符串对象都以PyASCIIObject开头:
typedef struct {
PyObject_HEAD
Py_ssize_t length;
Py_hash_t hash;
struct {
unsigned int interned:2;
unsigned int kind:2;
unsigned int compact:1;
unsigned int ascii:1;
unsigned int ready:1;
} state;
wchar_t *wstr;
} PyASCIIObject;
字符大小存储在 kind 位域中,如 PEP 和 code comments in unicodeobject 中所述:
00 => str is not initialized (data are in wstr)
01 => 1 byte (Latin-1)
10 => 2 byte (UCS-2)
11 => 4 byte (UCS-4);
在我们得到id(string)字符串的地址后,我们可以使用ctypes模块读取对象的字节(和kind字段):
import ctypes
mystr = "x"
first_byte = ctypes.c_uint8.from_address(id(mystr)).value
从对象开始到kind 的偏移量是PyObject_HEAD + Py_ssize_t length + Py_hash_t hash,这又是Py_ssize_t ob_refcnt + 指向ob_type 的指针+ Py_ssize_t length + 另一个指针的大小哈希类型:
offset = 2 * ctypes.sizeof(ctypes.c_ssize_t) + 2 * ctypes.sizeof(ctypes.c_void_p)
(在 x64 上是 32)
全部放在一起:
import ctypes
def bytes_per_char(s):
offset = 2 * ctypes.sizeof(ctypes.c_ssize_t) + 2 * ctypes.sizeof(ctypes.c_void_p)
kind = ctypes.c_uint8.from_address(id(s) + offset).value >> 2 & 3
size = {0: ctypes.sizeof(ctypes.c_wchar), 1: 1, 2: 2, 3: 4}
return size[kind]
给予:
>>> bytes_per_char('test')
1
>>> bytes_per_char('đžš')
2
>>> bytes_per_char('?')
4
请注意,我们必须处理 kind == 0 的特殊情况,因为字符类型正好是 wchar_t(16 位或 32 位,具体取决于平台)。