首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >无法使用ctypes.pythonapi调整元组的大小

无法使用ctypes.pythonapi调整元组的大小
EN

Stack Overflow用户
提问于 2019-12-08 19:57:36
回答 1查看 340关注 0票数 2

仅为了测试,我尝试使用ctypes重新调整元组的大小,结果非常糟糕:

代码语言:javascript
复制
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)

出什么问题了?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-12-08 21:35:22

您的代码有一些问题。

让我们从_PyTuple_Resize的签名开始,它是

代码语言:javascript
复制
int _PyTuple_Resize(PyObject **p, Py_ssize_t newsize)

也就是说,第一个参数不是py_object (它将是PyObject *p),而是py_object 引用通过,这意味着:

代码语言:javascript
复制
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

然后,上述链接的文档声明:

因为元组应该是不可变的,所以只有在只有一个对对象的引用时才应该使用元组。如果代码的其他部分可能已经知道元组,请不要使用此方法。

嗯,代码的其他部分都非常熟悉空元组:

代码语言:javascript
复制
import sys
a=()
sys.getrefcount(a)
# 28236

正如@CristiFati在评论中指出的那样,这是一个小的优化,因为元组是不可变的:所有空元组共享相同的单例。因此,在空元组上使用_PyTuple_Resize是很有问题的,即使在_PyTuple_Resize中捕捉到了这个角落的情况。

代码语言:javascript
复制
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用于程序的其他部分所不知道的元组:

代码语言:javascript
复制
b = c_ssize_t(2)
A=py_object(("no one knows me",))
pythonapi._PyTuple_Resize(byref(A), b) # returns 0 - means everything ok

我们得到一个处于不一致状态的对象:

代码语言:javascript
复制
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窃取了一个引用,所以我们也需要处理它:

代码语言:javascript
复制
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字节对齐的指针,因此与向字符串中添加字符不同,需要一个新的对象:

代码语言:javascript
复制
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-运行时内存分配程序管理,因此可以调整指针的大小:

代码语言:javascript
复制
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!

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59239358

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档