如何在两个不同的内存位置创建str“1”?

2024-09-24 22:30:24 发布

您现在位置:Python中文网/ 问答频道 /正文

我们可以通过这种方式击败小整数实习生(通过计算可以避免缓存层):

>>> n = 674039
>>> one1 = 1
>>> one2 = (n ** 9 + 1) % (n ** 9)
>>> one1 == one2
True
>>> one1 is one2
False

你如何击败小字符串实习生,即看到以下结果:

>>> one1 = "1"
>>> one2 = <???>
>>> type(one2) is str and one1 == one2
True
>>> one1 is one2
False

^{}提到“内部字符串不是不朽的”,但是没有上下文说明如何将字符串踢出内部,或者如何创建一个str实例来避免缓存层

因为实习是基于实施细节的,所以依赖于未记录的实施细节的答案是可以/预期的


Tags: and实例字符串falsetrueistype方式
1条回答
网友
1楼 · 发布于 2024-09-24 22:30:24

Unicode只包含一个字符(值小于128或更精确地从latin1)是最复杂的情况,因为这些字符串不是真正的interned,而是(更类似于整数池或与bytes的行为相同)在开始时创建,只要解释器处于活动状态are stored in an array

truct _Py_unicode_state {
    ...
    /* Single character Unicode strings in the Latin-1 range are being
       shared as well. */
    PyObject *latin1[256];
    ...
    /* This dictionary holds all interned unicode strings...
    */
    PyObject *interned;
    ...
};

因此,每次创建长度为1的unicode时,如果字符值位于latin1-数组中,则会查找该字符值。例如在^{}中:

/* ASCII is equivalent to the first 128 ordinals in Unicode. */
    if (size == 1 && (unsigned char)s[0] < 128) {
        if (consumed) {
            *consumed = 1;
        }
        return get_latin1_char((unsigned char)s[0]);
    }

有人甚至会说,如果有一种方法可以在解释器中绕过这一点——我们说的是一个(性能)bug

一种可能性是我们自己使用C-API填充unicode数据。我使用Cython来证明概念,但是ctypes也可以用于相同的效果:

%%cython
cdef extern from *:
    """
    PyObject* create_new_unicode(char *ch) 
    {
       PyUnicodeObject *ob = (PyUnicodeObject *)PyUnicode_New(1, 127);
       Py_UCS1 *data = PyUnicode_1BYTE_DATA(ob);
       data[0]=ch[0]; //fill data without using the unicode_decode_utf8
       return (PyObject*)ob;
    }
    """
    object create_new_unicode(char *ch)
    
def gen1():
    return create_new_unicode(b"1")

值得注意的细节:

  • PyUnicode_New将不会在latin1中查找,因为尚未设置字符
  • 为简单起见,以上仅适用于ASCII字符-因此我们将127作为maxchar传递给PyUnicode_New。因此,我们可以通过PyUnicode_1BYTE_DATA来解释数据,这样就可以轻松地手动操作数据,而无需太多麻烦

现在:

a,b=gen1(), gen1()
a is b, a == b
# yields (False, True)

如你所愿


下面是一个类似的想法,但通过ctypes实现:

from ctypes import POINTER, py_object, c_ssize_t, byref, pythonapi
PyUnicode_New = pythonapi.PyUnicode_New
PyUnicode_New.argtypes = (c_ssize_t, c_ssize_t)
PyUnicode_New.restype = py_object
PyUnicode_CopyCharacters = pythonapi._PyUnicode_FastCopyCharacters
PyUnicode_CopyCharacters.argtypes = (py_object, c_ssize_t, py_object, c_ssize_t, c_ssize_t)
PyUnicode_CopyCharacters.restype = c_ssize_t

def clone(orig):
    cloned = PyUnicode_New(1,127)
    PyUnicode_CopyCharacters(cloned, 0, orig, 0, 1)
    return cloned

值得注意的细节:

  • 不能将PyUnicode_1BYTE_DATA与ctypes一起使用,因为它是一个宏。另一种方法是计算到data-成员的偏移量,并直接访问该内存(但它取决于平台,感觉不是很可移植)
  • 作为解决方法,使用了PyUnicode_CopyCharacters(可能还有其他实现相同的可能性),这比直接计算/访问内存更抽象和可移植
  • 实际上,使用了_PyUnicode_FastCopyCharacters,因为PyUnicode_CopyCharacters将检查目标unicode是否有多个引用并抛出_PyUnicode_FastCopyCharacters不执行这些检查,而是按要求执行

现在:

a="1"
b=clone(a)
a is b, a==b
# yields (False, True)

对于长度超过1个字符的字符串,更容易避免插入,例如:

a="12"
b="123"[0:2]
a is b, a == b
#yields (False, True)

相关问题 更多 >