python中bytes类型与str类型的区别以及python中str类型是怎么存储的

python中bytes类型与str类型的区别以及python中str类型是怎么存储的

先说说bytes类型与str类型的区别, 它们是完全不同的两种类型. bytes实际上就是整数数组(0到127). 它对应着str的一种encoding方式. unicode不是编码方式, 只是指定了code points. 比如UTF-8, UTF-16才是具体的编码方式. str类型是本文重点要说的. 但是这里说的是它的不同, 不同就是它的目的是表示human readable的形式, 既然存在内存也需要编码, 但具体是哪种编码不一定.

根据这个回答, 用哪种存储方式由字符串来决定, 但一定不能是utf系列(utf-8/16/32), 因为必须用定长的存储方式. 可能是ascii, latin-1. 每一个字符可能用1/2/4字节存储, 总之必须定长.

以及写字符串到文件的时候, 会用哪种编码?

根据PEP393

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;

typedef struct {
  PyASCIIObject _base;
  Py_ssize_t utf8_length;
  char *utf8;
  Py_ssize_t wstr_length;
} PyCompactUnicodeObject;

typedef struct {
  PyCompactUnicodeObject _base;
  union {
      void *any;
      Py_UCS1 *latin1;
      Py_UCS2 *ucs2;
      Py_UCS4 *ucs4;
  } data;
} PyUnicodeObject;

原文中这个结构体其后的描述, 我觉得是这样的:

  • 注意到这是层次式的(a hierarchy of structures), 意思是说, PyCompactUnicodeObject中有一个PyASCIIObject _base; , 就是上一个结构体, 而PyUnicodeObject中又有PyCompactUnicodeObject _base;.

  • 不同的字符串会用不同的结构体, 具体来说:
    先引入compact的概念: Objects for which both size and maximum character are known at creation time are called "compact" unicode object

    我一开始看到的时候还奇怪, str不是可以做拼接么. 并不是, str就是常量, 拼接实际上是做替换. 有此为证:

    a="123"
    id(a)
    a+="shfkd"
    id(a) # 并不相同
    

    然后说, 不同的字符串具体会被分配为什么struct: 在compact的情况下, 最大字符<128, 用PyASCIIObject
    否则用PyCompactObject. 非compact, 用PyUnicodeObject

吐槽一下, 这个回答user2240578这个回答, 明显与str的表示毫无关系.

前面是阅读, 这个回答uniqueid的代码验证了这一点:

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

>>> getsizeof('U000000ff')  
74
>>> getsizeof('U000000ffU000000ff')# (+1 byte)  
75
>>> getsizeof('U00000100')  
76
>>> getsizeof('U00000100U00000100') # Size change at byte boundary(+2 bytes). Rep is UCS-2.   
78
>>> getsizeof('U0000ffff') 
76
>>> getsizeof('U0000ffffU0000ffff') # (+ 2 bytes)
78
>>> getsizeof('U00010000')  
80
>>> getsizeof('U00010000U00010000') # (+ 4 bytes) Thes size of the code unit changes to 4 at byte boundary again.
84

为什么从一个到第二个, 字符数相同, 但是内存差那么多, 因为用了嵌套结构体, 第一行0x7f=127, 在ascii的范围内, 用的是PyASCIIObject, 第二个用的是PyCompactUnicodeObject, 后者的结构体包含前者, 大那么多就是应该的了.

1字节的表示范围最大就是0xff, 可以看到8行与10行, 就是差1字节, 因为是0xff, 在表示范围内. 12和14, 是0x100, 超过1字节, 差时字节. 说明编码方式的确是由最大值决定的, 字符串与字符串的表示不同. 从18到20行, 再次发生了这种"跃迁", 因为2字节最大表示0xffff.

总结一下要点:

  • 不同字符串用的struct不同.
  • 不同字符串根据最大字符的范围, 用的编码方式不同. 但无论用哪种编码方式用的编码都必须是定长的, 因此不会用UTF-8之类的.