Python 3.x中字符串的内部表示是什么?

45
在Python 3.x中,字符串由Unicode ordinal项组成(请参见下面语言参考中的引用)。Unicode字符串的内部表示是什么?它是UTF-16吗?

字符串对象的项是Unicode代码单元。Unicode代码单元由一个项的字符串对象表示,可以存储表示Unicode序数的16位或32位值(序数的最大值在sys.maxunicode中给出,并且取决于Python在编译时的配置)。代理对可能存在于Unicode对象中,并将报告为两个单独的项。

8个回答

37

在实现PEP 393的Python 3.3中,内部表示将发生变化。新的表示形式将选择一个或多个ascii、latin-1、utf-8、utf-16、utf-32,通常试图获得一种紧凑的表示形式。

当与旧版API进行交互时(这些API仅存在于Windows系统中,其中wchar_t为两个字节),才会隐式转换为代理对;Python字符串将被保留。请参阅发行说明


10
在我看来,PEP 393 表示在给定特定字符串的情况下,内部表示是 ASCII、Latin-1(UCS1)、UCS2 或 UCS4 中最紧凑的。也就是说,它明确指出不是 utf-8/16/32。原因是 Python 必须是常量时间来索引字符串,因此字符必须具有统一的大小,而 UCS 满足这种情况,但 utf 表示则不行。 - gwideman
4
Latin-1 是 ASCII 的超集,因此没有将 ASCII 包括在选项中的必要。选项是(a) 统一为8位,即 Latin-1,(b) 统一为16位,即 UCS2,或者(c) 统一为32位,即 UCS4 (与 UTF-32 相同)。值得注意的是,UTF-8 和 UTF-16 被排除在外,因为它们每个代码点的位数不统一。 - JoelFan

26
在Python 3.3及以上版本中,字符串的内部表示将取决于字符串本身,并且可以是latin-1、UCS-2或UCS-4中的任何一种,如PEP 393所述。
对于早期版本的Python,内部表示取决于Python的构建标志。Python可以使用标志值--enable-unicode=ucs2--enable-unicode=ucs4进行构建。ucs2构建实际上使用UTF-16作为其内部表示,而ucs4构建使用UCS-4 / UTF-32。

这是正确的答案,应该被接受。 - scravy

10

查看CPython 3.1.5的源代码,在Include/unicodeobject.h中:

/* --- Unicode Type ------------------------------------------------------- */

typedef struct {
    PyObject_HEAD
    Py_ssize_t length;          /* Length of raw Unicode data in buffer */
    Py_UNICODE *str;            /* Raw Unicode buffer */
    long hash;                  /* Hash value; -1 if not set */
    int state;                  /* != 0 if interned. In this case the two
                                 * references from the dictionary to this object
                                 * are *not* counted in ob_refcnt. */
    PyObject *defenc;           /* (Default) Encoded version as Python
                                   string, or NULL; this is used for
                                   implementing the buffer protocol */
} PyUnicodeObject;

字符被存储为一个Py_UNICODE数组。在大多数平台上,我认为Py_UNICODE是用wchar_t进行#define的。


3

这要看情况:请参见这里。就内部表示而言,对于Python 3来说仍然如此。


3

内部表示形式从latin-1、UCS-2到UCS-4不等。UCS表示表示形式为2或4个字节长,unicode代码单元在数值上等于相应的代码点。我们可以通过查找代码单元大小变化的位置来检查此内容。

为了说明它们的范围从latin-1的1个字节到UCS-4的4个字节:

>>> getsizeof('')           
49
>>> getsizeof('a')  #------------------ + 1 byte as the representaion here is latin-1 
50
>>> getsizeof('\U0010ffff') 
80
>>> getsizeof('\U0010ffff\U0010ffff') # + 4 bytes as the representation here is UCS-4
84

我们可以检查一开始的表示确实是Latin-1而不是UTF-8,因为2字节代码单元的变化发生在字节边界而不是在UTF-8中的''\U0000007f'-'\U00000080'边界:

>>> getsizeof('\U0000007f')  
50
>>> getsizeof('\U00000080') #----------The size of the string changes at \x74 - \x80 boundary but..
74
>>> getsizeof('\U00000080\U00000080') # ..the size of the code-unit is still one. so not UTF-8
75

>>> getsizeof('\U000000ff')  
74
>>> getsizeof('\U000000ff\U000000ff')# (+1 byte)    
75
>>> getsizeof('\U00000100')  
76
>>> getsizeof('\U00000100\U00000100') # Size change at byte boundary(+2 bytes). Rep is UCS-2.             
78

>>> getsizeof('\U0000ffff') 
76
>>> getsizeof('\U0000ffff\U0000ffff') # (+ 2 bytes)
78
>>> getsizeof('\U00010000')            
80
>>> getsizeof('\U00010000\U00010000') # (+ 4 bytes) Thes size of the code unit changes to 4 at byte boundary again.
84

1
每个Python内部编码中的字符都被编码为4个字节。
>>> import array; s = 'Привет мир!'; b = array.array('u', s).tobytes(); print(b); print(len(s) * 4 == len(b))
b'\x1f\x04\x00\x00@\x04\x00\x008\x04\x00\x002\x04\x00\x005\x04\x00\x00B\x04\x00\x00 \x00\x00\x00<\x04\x00\x008\x04\x00\x00@\x04\x00\x00!\x00\x00\x00'
True
>>> import array; s = 'test'; b = array.array('u', s).tobytes(); print(b); print(len(s) * 4 == len(b))
b't\x00\x00\x00e\x00\x00\x00s\x00\x00\x00t\x00\x00\x00'
True
>>> 

你能否详细说明一下这段代码的作用?特别是那部分不是英文的 Привет мир! - shripal mehta
那个俄语片段用来描述在使用ASCII之外的字符时会发生什么。顺便说一下,这是俄语中的“Hello world!” - Adam Jenča

0

我认为,很难判断UTF-16和Python的字符串对象之间的区别,因为UTF-16只是一系列16位字,而Python的字符串对象则不同。

如果Python编译时使用了Unicode=UCS4选项,则将比较UTF-32和Python字符串。

因此,最好考虑它们属于不同的类别,尽管可以相互转换。


-1

Python 2.X和3.X之间的Unicode内部表示没有任何变化。

它绝对不是UTF-16。UTF-anything是面向字节的外部表示。

每个代码单元(字符,代理等)都被分配了一个来自range(0,2 ** 21)的数字。这被称为它的“序数”。

实际上,您引用的文档已经说明了一切。大多数Python二进制文件使用16位序数,这将限制您仅能使用基本多语言平面(“BMP”),除非您想要使用代理(如果您找不到头发衬衫并且您的钉床正在去锈)。要使用完整的Unicode字符集,您需要选择“宽构建”(32位宽)。

简而言之,在unicode对象中的内部表示是一个16位无符号整数数组,或者是一个32位无符号整数数组(仅使用21位)。


23
“将Unicode码点存储在16位整数中”被称为“UCS-2”。用32位整数执行相同操作的术语是UCS-4。 - Joachim Sauer
6
@John:我不明白你想表达什么意思。如果编码不是一种“存储Unicode代码点”(或更普遍地说,字符信息)的方式,那它是什么?是的,我非常清楚UTF-16是UCS-2的现代替代品,但正如你所说的那样,它们并不相同。UTF-16支持Unicode的所有字符,而UCS-2仅支持BMP字符集。 - Joachim Sauer
7
“目前还没有任何更改...” 请查看PEP 393(2010年1月),其中详细说明了随后发生的更改,以及另一个回答:“内部表示将会更改...” - gwideman
5
答案错误,请查看下面Tobu的答案。PEP 393是在此回答发布一个月后编写的。 - dotancohen
5
"UTF-anything是一种以字节为导向的外部表示方式。在一些系统中,它也可以作为有效的内部表示方式。例如,在许多基于C ++的系统中,UTF-8被用作内部编码方式,不仅仅是用于I/O操作。而Go语言则特别使用UTF-8作为其内部字符编码表示方式。" - Bjarke Ebert
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接