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
。成功返回0。如果 pos 越界,则返回 -1 并设置 IndexError 异常。p
这个说法不就等于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)`
PyTuple_Resize
功能
因为元组应该是不可变的,所以只能使用它 如果该对象只有一个引用。如果出现以下情况,请勿使用此功能: 代码的其他部分可能已经知道元组。元组 最终总会增长或收缩。将此视为破坏 旧元组并创建新元组,只会更有效。
看源码,函数上有守卫
if (!PyTuple_Check(op) || Py_REFCNT(op) != 1) {
.... error ....
果然,只有当只有 1 个对元组的引用时才允许这样做 - 该引用是认为更改它是个好主意的东西。因此,元组“大部分是不可变的”,但 C 代码可以在有限的情况下更改它,以避免创建新元组的惩罚。
文档中有一个注释说:
注意此函数“窃取”对 o 的引用并丢弃引用 到元组中受影响位置处已有的项目。
否则,在 Cpython Github 文档中,我们可以看到有关此函数的更多详细信息,尤其是此函数如何以及为何
steals
对象引用。我们可以读:
顺便说一句, :c:func:
是设置元组的唯一方法 项目; :c:func:PyTuple_SetItem
和 :c:func:PySequence_SetItem
拒绝这样做,因为元组是不可变的数据类型。你应该 仅对您正在创建的元组使用 :c:func:PyObject_SetItem
你自己。PyTuple_SetItem
可以使用以下命令编写填充列表的等效代码 :c:func:
和 :c:func:PyList_New
。PyList_SetItem
然而,在实践中,你很少会使用这些创建和创建的方式 填充元组或列表。有一个通用函数, :c:func:
,可以从 C 创建最常见的对象 值,由 :dfn:Py_BuildValue
. 指导format string