08月12, 2020

12. String Object 的底层也可变!

一般我们认为 String Object 是不可变的,所以它才能作为 dict 的 key,即可 hash。实际上在底层,String Object 可以在一些特定的情况下发生 “修改”,比如在字符串拼接时,调整前面 String object 的大小。

void
PyUnicode_Append(PyObject **p_left, PyObject *right)
{
    // 省略
    // 如果可更改,并且后面的 String Object 不宽于前面的 String Object。
    // 且不是 ascii 字符串。
    if (unicode_modifiable(left)
        && PyUnicode_CheckExact(right)
        && PyUnicode_KIND(right) <= PyUnicode_KIND(left)
        && !(PyUnicode_IS_ASCII(left) && !PyUnicode_IS_ASCII(right)))
    {
        /* append inplace */
        // 这里前面的字符串被充值大小
        if (unicode_resize(p_left, new_len) != 0)
            goto error;

        /* copy 'right' into the newly allocated area of 'left' */
        _PyUnicode_FastCopyCharacters(*p_left, left_len, right, 0, right_len);
    }
    else {
        // 省略
    }
}

当 right 被 append 到 left 时,如果 left 是可变更的,就会调整 left 的大小,就地将 right 的值复制到 left 的尾部。

img

为什么可以变更

String 对象作为不可变对象,其中一个主要考虑是它会被作为 dict 的键值,使用了其 hash 属性,如果字符串的值发生了变化,hash 值就会变。另一个问题在于,如果两个变量指向相同的 String Object,比如相同 String 对象的多个变量引用,或者是由于 interned 机制多个变量共同引用同一个字符串缓存,一旦这个 String Object 发生变化,会影响到其他变量,这是不允许的。

那么反之,如果一个 String Object 仅由当前变量引用,也没有用作任何 hash,也没有被缓存,不会共享给其他变量,那么这个 String Object 实际上就是可变的。下面的代码说明了这一点:

// Objects/unicodeobject.c:1898
static int
unicode_modifiable(PyObject *unicode)
{
    assert(_PyUnicode_CHECK(unicode));
    if (Py_REFCNT(unicode) != 1)
        return 0;
    if (_PyUnicode_HASH(unicode) != -1)
        return 0;
    if (PyUnicode_CHECK_INTERNED(unicode))
        return 0;
    if (!PyUnicode_CheckExact(unicode))
        return 0;
#ifdef Py_DEBUG
    /* singleton refcount is greater than 1 */
    assert(!unicode_is_singleton(unicode));
#endif
    return 1;
}

本文链接:http://www.thinkinpython.com/post/deep_python_vm_12.html

-- EOF --