如何找出内部字符串编码?

时间:2017-09-18 20:42:27

标签: python string python-3.x encoding python-internals

PEP 393我了解Python在存储字符串时可以在内部使用多种编码:latin1UCS-2UCS-4。是否有可能找出用于存储特定字符串的编码,例如在交互式口译员?

3 个答案:

答案 0 :(得分:2)

对于unicode对象的种类有一个CPython C API函数:PyUnicode_KIND

如果你有Cython和IPython 1 ,你可以轻松访问该功能:

In [1]: %load_ext cython
   ...:

In [2]: %%cython
   ...:
   ...: cdef extern from "Python.h":
   ...:     int PyUnicode_KIND(object o)
   ...:
   ...: cpdef unicode_kind(astring):
   ...:     if type(astring) is not str:
   ...:         raise TypeError('astring must be a string')
   ...:     return PyUnicode_KIND(astring)

In [3]: a = 'a'
   ...: b = 'Ǧ'
   ...: c = ''

In [4]: unicode_kind(a), unicode_kind(b), unicode_kind(c)
Out[4]: (1, 2, 4)

其中1代表latin-124分别代表UCS-2UCS-4

然后,您可以使用字典将这些数字映射到表示编码的字符串中。

1 如果没有Cython和/或IPython,这种组合也非常方便,否则会有更多的代码(没有IPython)和/或需要手动安装(没有Cython)。

答案 1 :(得分:0)

你可以从Python层测试这一点的唯一方法是(不通过ctypes或Python扩展模块手动调整对象内部)是通过检查字符串中最大字符的序数值,确定字符串是否存储为ASCII / latin-1,UCS-2或UCS-4。解决方案就像:

def get_bpc(s):
    maxordinal = ord(max(s, default='\0'))
    if maxordinal < 256:
        return 1
    elif maxordinal < 65536:
        return 2
    else:
        return 4

您实际上不能依赖sys.getsizeof,因为对于非ASCII字符串(即使每个字符串中的一个字节适合latin-1范围),字符串可能填充也可能不填充字符串的UTF-8表示,以及为其添加额外字符和比较大小的技巧实际上可以显示减少的大小,并且它实际上可以“远距离”发生,所以你不是直接负责您正在检查的字符串上是否存在缓存的UTF-8表单。例如:

>>> e = 'é'
>>> sys.getsizeof(e)
74
>>> sys.getsizeof(e + 'a')
75
>>> class é: pass  # One of several ways to trigger creation/caching of UTF-8 form
>>> sys.getsizeof(e)
77  # !!! Grew three bytes even though it's the same variable
>>> sys.getsizeof(e + 'a')
75  # !!! Adding a character shrunk the string!

答案 2 :(得分:0)

找出CPython用于特定unicode字符串的确切内部编码的一种方法是查看实际的(CPython)对象。

根据PEP 393Specification部分),所有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) +散列类型的另一个指针的大小:

32

(x64上为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的特殊情况,因为字符类型正好是{{1}}(16或32位,具体取决于平台)。