仅为了测试,我尝试使用ctypes重新调整元组的大小,结果非常糟糕:
Python 3.6.9 (default, Nov 7 2019, 10:44:02)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import py_object, c_long, pythonapi
>>> _PyTuple_Resize = pythonapi._PyTuple_Resize
>>> _PyTuple_Resize.argtypes = (py_object, c_long)
>>> a = ()
>>> b = c_long(1)
>>> _PyTuple_Resize(a, b)
Segmentation fault (core dumped)出什么问题了?
发布于 2019-12-08 21:35:22
您的代码有一些问题。
让我们从_PyTuple_Resize的签名开始,它是
int _PyTuple_Resize(PyObject **p, Py_ssize_t newsize)也就是说,第一个参数不是py_object (它将是PyObject *p),而是py_object 引用通过,这意味着:
from ctypes import POINTER, py_object, c_ssize_t, byref, pythonapi
_PyTuple_Resize = pythonapi._PyTuple_Resize
_PyTuple_Resize.argtypes = (POINTER(py_object), c_ssize_t)但是,没有必要定义_PyTuple_Resize的参数(如任何其他肾盂功能),如果不是int (但在_PyTuple_Resize情况下),则只需要定义restype。
然后,上述链接的文档声明:
因为元组应该是不可变的,所以只有在只有一个对对象的引用时才应该使用元组。如果代码的其他部分可能已经知道元组,请不要使用此方法。
嗯,代码的其他部分都非常熟悉空元组:
import sys
a=()
sys.getrefcount(a)
# 28236正如@CristiFati在评论中指出的那样,这是一个小的优化,因为元组是不可变的:所有空元组共享相同的单例。因此,在空元组上使用_PyTuple_Resize是很有问题的,即使在_PyTuple_Resize中捕捉到了这个角落的情况。
if (oldsize == 0) {
/* Empty tuples are often shared, so we should never
resize them in-place even if we do own the only
(current) reference */
Py_DECREF(v);
*pv = PyTuple_New(newsize);
return *pv == NULL ? -1 : 0;
}但是,我的观点是,在调用_PyTuple_Resize之前,必须确保没有其他引用。
现在,即使将_PyTuple_Resize用于程序的其他部分所不知道的元组:
b = c_ssize_t(2)
A=py_object(("no one knows me",))
pythonapi._PyTuple_Resize(byref(A), b) # returns 0 - means everything ok我们得到一个处于不一致状态的对象:
print(A)
# py_object(('no one knows me', <NULL>))问题是NULL-pointer作为第二个元素:现在,使用A.value的许多操作(如print(A.value))会分段故障或导致其他问题。
因此,现在需要使用PyTuple_SetItem (它正确地处理NULL元素,并且不试图减少对空指针的引用)来在元组中设置NULL-elements,然后才能使用A.value完成任何操作。顺便说一句。通常,对新创建的元组/元素使用PyTuple_SET_ITEM,但它是定义,因此不是pythonapi的一部分。
由于PyTuple_SetItem窃取了一个引用,所以我们也需要处理它:
B=py_object(666)
pythonapi.Py_IncRef(B)
pythonapi.PyTuple_SetItem(A,1,B)
print(A.value)
# ('no one knows me', 666)对于小元组,_PyTuple_Resize总是(对于64位构建)创建一个新的元组对象,而不是重用旧的元组对象,因为添加元素意味着向内存占用区(至少是64位构建)添加8个字节,而pymalloc返回对8字节对齐的指针,因此与向字符串中添加字符不同,需要一个新的对象:
b = c_ssize_t(2)
A=py_object(("no one knows me",))
print(id(A.value))
# 2311126190344
pythonapi._PyTuple_Resize(byref(A), b)
print(id(A.value))
# 2311143455304我们看到了不同的身份证!
但是,对于内存占用大于512字节的元组对象,内存由底层c-运行时内存分配程序管理,因此可以调整指针的大小:
b = c_ssize_t(1002)
A=py_object(("no one knows me",)*1000)
print(id(A.value))
# 2350988176984
pythonapi._PyTuple_Resize(byref(A), b)
print(id(A.value))
# 2350988176984现在,旧对象被扩展了--并且保留了id!
https://stackoverflow.com/questions/59239358
复制相似问题