python 内核分析(五):Python字符串类型对象

本系列作为本人@takooctopus深入学习python机制的记录,这个博客遵照着栖迟于一丘的博客上面的流程进行的,也包含我在实际查看源码时的感想,特此列出,表示感谢。

关于python3

在看任何语言的字符串之前,我们首先应该看其编码问题。

python3中默认使用unicode编码,我们找到它的定义:


{{- code -}}

[Objects/unicodeoject.c]中,提供了许多创建unicode对象的方法。我们考虑字符串,所以寻找PyUnicode_FromString()


{{- code -}}

在这里,其设置了一个数据的上限PY_SSIZE_T_MAX,其值为((Py_ssize_t)(((size_t)-1)>>1)),考虑我们64位系统,size_t__int64

明显的,当其没有溢出时,就转入下一步。


{{- code -}}

PyUnicode_DecodeUTF8Stateful()这个函数就会自动判断格式。

字符串对象共享机制

我们在之前讨论过了小整数的机制,其预先实现了小整数的dict,再对其进行引用。

字符串也同样有这种类似的引用机制itern


{{- code -}}

传入的字符串首先经过类型和状态检查PyUnicode_Check()PyUnicode_CheckExact()PyUnicode_CHECK_INTERNED()。如果都通过,且没有被interned,就新建dict。

特别的Py_REFCNT(s) -= 2,由于对象添加进入dict时会对对其引用进行两次+1操作,所以需要对引用-2平衡。

注意其仍旧是生成了一个临时字符串对象,在引用交接后将其销毁了。

以c的字符串创建一样需要创建对象:


{{- code -}}

我们看见字符串对象有属性interned,在经历了PyUnicode_InternInPlace()后会改变,其值有两种选择SSTATE_INTERNED_MORTALSSTATE_INTERNED_IMMORTAL,分别对应了其是否会被销毁。

我们可以在PyUnicode_InternImmortal()中更改状态。


{{- code -}}

字符缓冲池

单个unicode字符就直接以一个缓冲池保存


{{- code -}}

先对所创建的字符串(只有一个字符)对象进行intern操作,再将inter的结果缓存到字符缓冲池unicode_latin1中,两者都是指向同一个字符对象。

PyUnicodeObject拼接

我们上面讲了每个PyUnicodeObject对象都是一个不可变对象。我们使用+来拼接字符串会导致n-1次的内存搬运工作。


{{- code -}}

官方推荐使用join操作。


{{- code -}}

再看PyUnicode_Join()


{{- code -}}

再我们看_PyUnicode_JoinArray()的实现:


{{- code -}}

其在统计了list中的对象及长度后,统一进行申请空间并逐一进行拷贝。