Python源码剖析 - Python中的字符串对象

1. 前言

我们已经在 【Python中的整数对象】 章节中对定长对象进行了详细的讲解,接下来我们将介绍变长对象,而字符串类型,则是这类对象的典型代表。

这里必须先引入一个概念:

Python 中的变长对象分为两类

  • 变长可变对象 - 例如 List,创建后还能添加、删除元素
  • 变长不可变对象 - 例如 StringTuple, 创建后,不再支持添加、删除等操作

2. PyStringObject初识

PyStringObject 是对字符串对象的实现方式。首先它是一个可变长度的对象,这个可变是只指在创建字符串对象的时候,这个长度并不固定。但是一旦创建完毕后,这个长度就固定了,不能再发生变化。

举例来说:

test_str = "Hello World"
test_url = "https://www.xtuz.net"

显而易见,test_str 的长度和 test_url 的长度并不一样,这个原因就是在字符串对象创建时 PyStringObject 并不限定长度,然后创建完毕后,改对象内部维护的字符串对象就不在改变了。

我们从源码中也可以进行佐证:

typedef struct {
    PyObject_VAR_HEAD
    long ob_shash;
    int ob_sstate;
    char ob_sval[1];

    /* Invariants:
     *     ob_sval contains space for 'ob_size+1' elements.
     *     ob_sval[ob_size] == 0.
     *     ob_shash is the hash of the string or -1 if not computed yet.
     *     ob_sstate != 0 iff the string object is in stringobject.c's
     *       'interned' dictionary; in this case the two references
     *       from 'interned' to this object are *not counted* in ob_refcnt.
     */
} PyStringObject;

PyObject_VAR_HEAD 中的 ob_size (详见Python源码剖析 - 对象初探),记录变长对象的内存大小,ov_sval 作为字符指针指向一段内存,这段内存就是实际字符串。比例 test_str 的 ob_size 则为11。

ob_shash 则是该对象的哈希值,这在 dict 类型中是非常有用的,作为 key 值存在。

ob_sstate 则是表明该对象是否经过 intern 机制处理,简单来说就是即值同样的字符串对象仅仅会保存一份,放在一个字符串储蓄池中,是共用的,当然,肯定不能改变,这也决定了字符串必须是不可变对象。

3. PyStringObject创建

从代码上来看,可以有多种创建 PyStringObject 的方式:

PyAPI_FUNC(PyObject *) PyString_FromStringAndSize(const char *, Py_ssize_t);
PyAPI_FUNC(PyObject *) PyString_FromString(const char *);
PyAPI_FUNC(PyObject *) PyString_FromFormatV(const char*, va_list)
                                Py_GCC_ATTRIBUTE((format(printf, 1, 0)));
PyAPI_FUNC(PyObject *) PyString_FromFormat(const char*, ...)
                                Py_GCC_ATTRIBUTE((format(printf, 1, 2)));

其中,最常用的则是 PyString_FromString(const char *);

代码实现如下:

PyObject *
PyString_FromString(const char *str)
{
    register size_t size;
    register PyStringObject *op;

    assert(str != NULL);
    size = strlen(str);
    if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) {
        PyErr_SetString(PyExc_OverflowError,
            "string is too long for a Python string");
        return NULL;
    }
    if (size == 0 && (op = nullstring) != NULL) {
#ifdef COUNT_ALLOCS
        null_strings++;
#endif
        Py_INCREF(op);
        return (PyObject *)op;
    }
    if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {
#ifdef COUNT_ALLOCS
        one_strings++;
#endif
        Py_INCREF(op);
        return (PyObject *)op;
    }

    /* Inline PyObject_NewVar */
    op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size);
    if (op == NULL)
        return PyErr_NoMemory();
    (void)PyObject_INIT_VAR(op, &PyString_Type, size);
    op->ob_shash = -1;
    op->ob_sstate = SSTATE_NOT_INTERNED;
    Py_MEMCPY(op->ob_sval, str, size+1);
    /* share short strings */
    if (size == 0) {
        PyObject *t = (PyObject *)op;
        PyString_InternInPlace(&t);
        op = (PyStringObject *)t;
        nullstring = op;
        Py_INCREF(op);
    } else if (size == 1) {
        PyObject *t = (PyObject *)op;
        PyString_InternInPlace(&t);
        op = (PyStringObject *)t;
        characters[*str & UCHAR_MAX] = op;
        Py_INCREF(op);
    }
    return (PyObject *) op;
}

简单来说,主要是三个逻辑:

  1. 判断字符串是否过长,过长,则返回 null 指针
  2. 判断是否是空串,空串,则则将引用
  3. 分配内存,并将字符串复制到 op->ob_sval 中

在完成创建后,内存布局如上所示

4. 字符缓冲池

我们已经在【Python中的整数对象】 中阐述了 Python 对小整数的优化处理,而字符串的intern机制与此类似,其实就是会为长度为1的的字符创建对象池。

    if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {
#ifdef COUNT_ALLOCS
        one_strings++;
#endif
        Py_INCREF(op);
        return (PyObject *)op;
    }


/* share short strings */
    if (size == 0) {
        PyObject *t = (PyObject *)op;
        PyString_InternInPlace(&t);
        op = (PyStringObject *)t;
        nullstring = op;
        Py_INCREF(op);
    } else if (size == 1) {
        PyObject *t = (PyObject *)op;
        PyString_InternInPlace(&t);
        op = (PyStringObject *)t;
        characters[*str & UCHAR_MAX] = op;
        Py_INCREF(op);
    }

每当创建长度为1的字符串的时候,都会把它存到 characters 里面,这样之后创建长度为1的字符时,如果检测到已经在characters里面了,就直接返回这个缓冲的对象,不用进行malloc,这也就是该缓冲池的作用。

5. 字符串对象的intern机制

在 CPython 中字符串的实现原理使用了一种叫做 Intern(字符串驻留)的技术来提高字符串效率。

先来看一段代码:

a='www.xtuz.net'
b='www.xtuz.net'
print(id(a), id(b))
print(a is b)

可以看到如下输出结果

(4420449312, 4420449312)
True

a 和 b 虽然值是一样的,但确确实实是两个不同的字符串对象,Python 会为它们俩各自分配一段内存空间,假设程序中存在大量值相同的字符串,系统就不得不为每个字符串重复地分配内存空间,显然,对系统来说是一种无谓的资源浪费。为了解决这种问题,Python 引入了 intern 机制。

Intern 是 Python 中的一个内建函数,该函数的作用就是对字符串进行 intern 机制处理,处理后返回字符串对象。我们发现但凡是值相同的字符串经过 intern 机制处理之后,返回的都是同一个字符串对象,这种方式在处理大数据的时候无疑能节省更多的内存空间,系统无需为相同的字符串重复分配内存,对于值相同的字符串共用一个对象即可。

Intern 实现 机制的方式非常简单,就是通过维护一个字符串储蓄池,这个池子是一个字典结构,如果字符串已经存在于池子中了就不再去创建新的字符串,直接返回之前创建好的字符串对象,如果之前还没有加入到该池子中,则先构造一个字符串对象,并把这个对象加入到池子中去,方便下一次获取。

6. 更多内容

原文来自兔子先生网站:https://www.xtuz.net/detail-139.html

查看原文 >>> Python源码剖析 - Python中的字符串对象

如果你对Python语言感兴趣,可以关注我,或者关注我的微信公众号:xtuz666


相关主题:
相关推荐
  1. 易中天给英雄武汉的一首诗 - 武汉新鲜事
  2. 圣萨瓦和魔鬼的故事
  3. 辽宁省沈阳市新民市智慧幼儿园 - 沈阳幼儿园黄页
  4. 贵溪县文坊镇塘头村小学
  5. 魔戒
  6. 事发武汉农夫山泉矿泉水内惊现大量虫卵
  7. 婴儿睡觉哼哼唧唧扭来扭去什么原因 - 育儿经验
  8. 邯郸市永年县星光幼儿园(小南门街) - 邯郸幼儿园黄页
  9. Javascript和Java的关系
  10. 警方通报:武汉男子持刀至五死一伤,嫌犯已跳桥
  11. 佛山市顺德区杏坛镇新联小学
  12. 武汉00后大二学妹拍视频月入七十万
  13. 化妆的乌鸦
  14. 蝙蝠的经验
  15. 奇奇当司机
  16. Python+Selenium基本操作获取当前页面URL
  17. 益智小故事十则
  18. Python公开课 - Appium基本介绍
  19. 武汉一女子七天被骗220万
  20. 垦利县胜坨镇三海小学
  21. 临沂市胜利小学
  22. Python公开课 - Python与操作系统
  23. 饥饿的毛毛虫
  24. 百名网媒总编辑登上知音号欣赏武汉夜色
  25. 武汉动物园火烈鸟孔雀排队打疫苗
  26. Python实战 - Django自动化创建sitemap
  27. 脱单!武汉的相亲角在哪你知道吗?
  28. 未来星双语幼儿园 - 石家庄幼儿园黄页
  29. 天骄幼儿园(红缨) - 海南省幼儿园黄页
  30. 奥迪客户不满加价,被武汉4S店销售群殴
  31. 江门市新会区大泽镇大泽吕金铨学校(小学部)
  32. 湖北将发放消费券 - 武汉新鲜事
  33. 上海市黄浦区第一中心小学
  34. 西安市临潼区春苗幼儿园(208县道) - 西安幼儿园黄页
  35. 技术破解钉钉打卡考勤 - 苹果手机专用完整解决方案
  36. 夜莺的悲哀
  37. 上海市浦东新区米奇幼儿园 - 上海幼儿园黄页
  38. 梦里的烙饼
  39. 长沙市火星小学
  40. 沈阳市河北街第二小学(河北二校)
  41. 深圳市龙岗区坂田街道岗头村幼儿园 - 深圳幼儿园黄页
  42. 安新县七级幼儿园 - 保定幼儿园黄页
  43. Python公开课 - 异常处理
  44. Python公开课 - Django自动添加Last-Modified和ETag
  45. Centos7中配置 Putty免密码登录
  46. 太原市万柏林区新育幼儿园 - 太原幼儿园黄页
  47. asyncio简明教程
  48. 珠海市斗门区井岸镇第二小学
  49. 礼轻人意重
  50. 芝罘区刘家小学