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

Abd*_*P M 12 python ctypes tuples cpython python-3.x

中的元组在设计上是不可变的,因此如果我们尝试改变元组对象,会发出以下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
Run Code Online (Sandbox Code Playgroud)

所以我的问题是,如果元组在设计上是不可变的,为什么cpython 公开PyTuple_SetItem为 C-API

从文档中它被描述为

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

o在 指向的元组的 pos 位置插入对对象的引用p0成功归来。如果 pos 越界,则返回-1并设置 IndexError 异常。

这句话不是和tuple[index] = valuepython层中的完全一样吗?如果目标是从项目集合创建一个元组,我们可以使用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)`
Run Code Online (Sandbox Code Playgroud)

tde*_*ney 11

同样,有一个PyTuple_Resize带有警告的函数

因为元组应该是不可变的,所以只有在只有一个对象引用时才应该使用它。如果代码的其他部分可能已经知道该元组,请勿使用此选项。元组总是会在最后增大或缩小。将此视为销毁旧元组并创建新元组,只会更有效。

看源码,函数上有一个guard

if (!PyTuple_Check(op) || Py_REFCNT(op) != 1) {
    .... error ....
Run Code Online (Sandbox Code Playgroud)

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