如果元组在设计上是不可变的,为什么 cpython 将“PyTuple_SetItem”公开为 C-API?

问题描述 投票:0回答:2

Python 中的元组在设计上是不可变的,因此如果我们尝试改变元组对象,Python 会发出以下有意义的

TypeError

>>> a = (1, 2, 3)
>>> a[0] = 12
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

所以我的问题是,如果元组在设计上是不可变的,为什么 cpython 将

PyTuple_SetItem
公开为 C-API?.

从文档中描述为

int PyTuple_SetItem(PyObject *p, Py_ssize_t pos, PyObject *o)

在指向元组的位置 pos 处插入对对象

o
的引用 由
p
。成功返回0。如果 pos 越界,则返回 -1 并设置 IndexError 异常。

这个说法不就等于python层的

tuple[index] = value
吗?如果目标是从项目集合创建一个元组,我们可以使用
PyTuple_Pack

补充说明:

经过大量尝试和错误

ctypes.pythonapi
我设法使用
PyTuple_SetItem

改变元组对象
import ctypes

from ctypes import py_object

my_tuple = (1, 2, 3)
newObj = py_object(my_tuple)

m = "hello"

# I don't know why I need to Py_DecRef here. 
# Although to reproduce this in your system,  no of times you have 
# to do `Py_DecRef` depends on no of ref count of `newObj` in your system.
ctypes.pythonapi.Py_DecRef(newObj)
ctypes.pythonapi.Py_DecRef(newObj)
ctypes.pythonapi.Py_DecRef(newObj)

ctypes.pythonapi.Py_IncRef(m)



PyTuple_SetItem = ctypes.pythonapi.PyTuple_SetItem
PyTuple_SetItem.argtypes = ctypes.py_object, ctypes.c_size_t, ctypes.py_object

PyTuple_SetItem(newObj, 0, m)
print(my_tuple) # this will print `('hello', 2, 3)`
python python-3.x tuples ctypes cpython
2个回答
11
投票

同样,还有一个带有警告的

PyTuple_Resize
功能

因为元组应该是不可变的,所以只能使用它 如果该对象只有一个引用。如果出现以下情况,请勿使用此功能: 代码的其他部分可能已经知道元组。元组 最终总会增长或收缩。将此视为破坏 旧元组并创建新元组,只会更有效。

看源码,函数上有守卫

if (!PyTuple_Check(op) || Py_REFCNT(op) != 1) {
    .... error ....

果然,只有当只有 1 个对元组的引用时才允许这样做 - 该引用是认为更改它是个好主意的东西。因此,元组“大部分是不可变的”,但 C 代码可以在有限的情况下更改它,以避免创建新元组的惩罚。


3
投票

文档中有一个注释说:

注意此函数“窃取”对 o 的引用并丢弃引用 到元组中受影响位置处已有的项目。

否则,在 Cpython Github 文档中,我们可以看到有关此函数的更多详细信息,尤其是此函数如何以及为何

steals
对象引用。我们可以读:

顺便说一句, :c:func:

PyTuple_SetItem
是设置元组的唯一方法 项目; :c:func:
PySequence_SetItem
和 :c:func:
PyObject_SetItem
拒绝这样做,因为元组是不可变的数据类型。你应该 仅对您正在创建的元组使用 :c:func:
PyTuple_SetItem
你自己。

可以使用以下命令编写填充列表的等效代码 :c:func:

PyList_New
和 :c:func:
PyList_SetItem

然而,在实践中,你很少会使用这些创建和创建的方式 填充元组或列表。有一个通用函数, :c:func:

Py_BuildValue
,可以从 C 创建最常见的对象 值,由 :dfn:
format string
.

指导
© www.soinside.com 2019 - 2024. All rights reserved.