美文网首页
【Python】字符串

【Python】字符串

作者: lndyzwdxhs | 来源:发表于2018-11-10 15:14 被阅读6次

0x01 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;
  • PyStringObject是一个可变长度内存的对象,也是一个不可变对象。即当创建了一个PyStringObject对象以后,该对象内部维护的字符串就不能被改变了。所以PyStringObject对象可以作为字典的Key使用
  • ob_size保存着对象中维护的可变长度内存的数量;ob_sval是一个字符的字符数组,其实它是作为一个字符指针指向一段内存的,这段内存的实际长度就是由ob_size来维护的。这个机制是python中所有可变长对象的实现机制
  • PyStringObject内部维护的字符串末尾必须以'\0'结尾,和C语言字符串一样。所以ob_sval指向的是一段长度为ob_size+1个字节的内存,而且ob_sval[ob_size] == '\0'
  • ob_shash变量的作用是缓存该对象的hash值,可以避免每一次都需要计算该对象的hash值。默认值是-1这个hashPyDictObject对象中发挥了很大作用
  • ob_sstate变量标记了该对象是否经过了intern机制的处理。
  • 预存字符串的hash值intern机制使Python虚拟机执行效率提升了20%

0x02 PyString_Type

PyTypeObject PyString_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "str",
    sizeof(PyStringObject),
    sizeof(char),
    string_dealloc,             /* tp_dealloc */
    (printfunc)string_print,        /* tp_print */
    0,                  /* tp_getattr */
    0,                  /* tp_setattr */
    0,                  /* tp_compare */
    string_repr,                /* tp_repr */
    &string_as_number,          /* tp_as_number */
    &string_as_sequence,            /* tp_as_sequence */
    &string_as_mapping,         /* tp_as_mapping */
    (hashfunc)string_hash,          /* tp_hash */
    0,                  /* tp_call */
    string_str,             /* tp_str */
    PyObject_GenericGetAttr,        /* tp_getattro */
    0,                  /* tp_setattro */
    &string_as_buffer,          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
        Py_TPFLAGS_BASETYPE,        /* tp_flags */
    string_doc,             /* tp_doc */
    0,                  /* tp_traverse */
    0,                  /* tp_clear */
    (richcmpfunc)string_richcompare,    /* tp_richcompare */
    0,                  /* tp_weaklistoffset */
    0,                  /* tp_iter */
    0,                  /* tp_iternext */
    string_methods,             /* tp_methods */
    0,                  /* tp_members */
    0,                  /* tp_getset */
    &PyBaseString_Type,         /* tp_base */
    0,                  /* tp_dict */
    0,                  /* tp_descr_get */
    0,                  /* tp_descr_set */
    0,                  /* tp_dictoffset */
    0,                  /* tp_init */
    0,                  /* tp_alloc */
    string_new,             /* tp_new */
    PyObject_Del,                       /* tp_free */
};
  • tp_itemsize被设置为sizeof(char),即一个字节。对于Python中所有变长对象,必须设置tp_itemsize大小。因为知道了每一个元素的大小,才能和ob_size配合计算出需要申请的内存大小:malloc(tp_itemsize * ob_size)

0x03 intern机制

void
PyString_InternInPlace(PyObject **p)
{
    register PyStringObject *s = (PyStringObject *)(*p);
    PyObject *t;
    if (s == NULL || !PyString_Check(s))
        Py_FatalError("PyString_InternInPlace: strings only please!");
    /* If it's a string subclass, we don't really know what putting
       it in the interned dict might do. */
    if (!PyString_CheckExact(s))
        return;
    if (PyString_CHECK_INTERNED(s))
        return;

    // 创建字典对象interned,用于记录经过intern机制处理后的PyStringObject对象
    if (interned == NULL) {
        interned = PyDict_New();
        if (interned == NULL) {
            PyErr_Clear(); /* Don't leave an exception */
            return;
        }
    }

    // 检查对象s,是否已经在interned字典中了
    t = PyDict_GetItem(interned, (PyObject *)s);
    if (t) {
        Py_INCREF(t);
        Py_DECREF(*p);
        *p = t;
        return;
    }

    //对象s没有在interned字典中了,将它加入到字典中
    if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) {
        PyErr_Clear();
        return;
    }
    /* The two references in interned are not counted by refcnt.
       The string deallocator will take care of this */
    s->ob_refcnt -= 2;

    // 修改对象s的ob_sstate状态
    PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL;
}
  • PyStringObject对象的intern机制的目的是:对于被intern的字符串A,在整个python运行期间,系统中都只有唯一的一个与字符串A对应的PyStringObject对象。也就是对于简单的字符串使用了intern机制以后,可以节省很多内存空间,而且比较两个PyStringObject对象是否相同,也只需要比较他们的PyObject *是否相同即可,不需要进行内部字符串的比较。
  • 函数内先检查对象是否是PyStringObject对象,intern机制只能应用在PyStringObject对象上,它的派生类也不能使用
  • static PyObject *interned;被定位为一个静态全局变量。
  • intern机制图解
  • 如果PyStringObject对象s没有在interned中找到,需要将它加入到interned字典中。如上图所示,将s对象加入到interned字典中时,因为字典的keyvalue都引用了s,所以s的引用计数默认增加了2次,但是python规定interneds的指针不能视为对象s的有效引用(因为如果是有效引用的话,在python结束之前,s的引用计数永远不可能为0s永远无法删除),所以最后需要将s的引用计数减2s->ob_refcnt -= 2;
  • intern处理后的对象有两种状态:SSTATE_INTERNED_MORTALSSTATE_INTERNED_IMMORTAL
    • SSTATE_INTERNED_IMMORTAL状态的对象永远不会被销毁
    • SSTATE_INTERNED_MORTAL状态的对象如果引用计数为0了,会被销毁
  • PyString_InternInPlace()函数只能创建SSTATE_INTERNED_MORTAL状态的对象,SSTATE_INTERNED_IMMORTAL状态对象需要调用PyString_InternImmortal()函数。

0x04 创建PyStringObject对象

从C中原生的字符串创建PyStringObject对象

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 - sizeof(PyStringObject)) {
        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(sizeof(PyStringObject) + size);
    if (op == NULL)
        return PyErr_NoMemory();
    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;
}
  • 首先检查字符串长度是否超过最大限度PY_SSIZE_T_MAX,这个值是平台相关的。
  • 然后检查是否是空串,python对于空串有特殊的处理(static PyStringObject *nullstring)。维护了一个全局空串指针对象(nullstring),只需要第一次对空串进行创建对象,然后通过intern机制共享,以后可以直接使用。
  • 接下来判断是否是单个字符(ASCII范围内),Python对于单个字符的情况,全局维护了一个
    characters指针数组,指向ASCII中的255个字符,第一次都会创建相应的字符对象,然后intern共享,之后直接使用相应的对象即可。
  • 最后对于字符串的情况才进行申请内存,创建对象。ob_shash设置为-1ob_sstate设置为SSTATE_NOT_INTERNED,字符串内容拷贝到op->ob_sval中进行管理,字符串后面的'\0'也需要拷贝,所以size+1了(Py_MEMCPY(op->ob_sval, str, size+1);

根据字符串和给定大小来创建PyStringObject对象

  • 这种创建方式和第一种一样,区别在于第一种需要传入C字符串(最后以'\0'结尾),因为没有'\0'不知道字符串从哪结束;这种方式不需要字符串以'\0'结尾,因为这里给定了字符串的大小,可以通过大小控制字符串的范围。

0x05 字符缓冲池

static PyStringObject *characters[UCHAR_MAX + 1];
  • python对于字符串的缓冲策略就是对单字符进行缓冲。
  • 以静态全局变量来存储,默认全为空指针。
  • 针对某个单字符对象时,只在第一次使用时创建对象,然后intern共享,然后存储到characters指针数组中,之后相同的字符直接使用该对象。

0x06 PyStringObject效率相关问题

字符串连接

大家都知道字符串连接一般通过"+"操作符来连接,殊不知这是影响效率的根源。

诚然,PyStringObject对象是一个不可变对象,连接时需要创建一个新的PyStringObject对象,这样的话对N个字符串进行连接,就要创建N-1次内存申请工作,极大的影响了效率。

推荐使用PyStringObject对象的join操作,它只需要申请一次内存。

  • 统计出需要连接的list中的PyStringObject对象个数,然后遍历list,统计出list中的所有字符串的长度,然后根据这个长度去申请内存,然后将list中的字符创拷贝到新申请的内存中,所以在这里只需要申请一次内存,就将N个字符串连接起来了,而且N越大,效率提升越明显。

欢迎关注微信公众号(coder0x00)或扫描下方二维码关注,我们将持续搜寻程序员必备基础技能包提供给大家。


相关文章

  • python基础知识(3)

    python字符串 python转义字符 python字符串运算符 python字符串格式化 python格式化操...

  • python count()方法详解

    Python count()方法 Python 字符串 描述 Python count() 方法用于统计字符串里某...

  • python字符串格式化符号与内建函数资料表

    python字符串格式化符号: Python 的字符串内建函数 Python 的字符串常用内建函数如下:

  • 字符串操作方法

    Python3字符串 Python访问字符串中的值 Python中的字符串用单引号(')或双引号(")括起来,同时...

  • python字符串相关函数

    Python3字符串 Python访问字符串中的值 Python中的字符串用单引号(')或双引号(")括起来,同时...

  • 2018-09-28自学习资料

    Python3字符串 Python访问字符串中的值 Python中的字符串用单引号(')或双引号(")括起来,同时...

  • 字符串内置函数

    Python3字符串 Python访问字符串中的值 Python中的字符串用单引号(')或双引号(")括起来,同时...

  • 2018-07-18 字符串资料(函数方法)

    Python3字符串资料 Python访问字符串中的值 Python中的字符串用单引号(')或双引号(")括起来,...

  • day4 字符串自主操作

    Python3字符串 Python访问字符串中的值 Python中的字符串用单引号(')或双引号(")括起来,同时...

  • Day3-整理

    Python3字符串 Python访问字符串中的值 Python中的字符串用单引号(')或双引号(")括起来,同时...

网友评论

      本文标题:【Python】字符串

      本文链接:https://www.haomeiwen.com/subject/alvzxqtx.html