Есть некоторые проблемы с вашим кодом.
Начнем с подписи _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
(как любой другая функция pythonapi), нужно определить restype
только в том случае, если это не int
(но это в случае _PyTuple_Resize
).
Затем в приведенной выше связанной документации говорится:
Поскольку кортежи предполагаются неизменяемыми, это следует использовать только в том случае, если на объект имеется только одна ссылка. Не используйте это, если кортеж уже может быть известен какой-либо другой части кода.
Что ж, пустой кортеж хорошо известен другим частям кода:
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
в качестве второго элемента: теперь многие операции (например, print(A.value)
) с A.value
будут segfault или приведут к другим проблемам.
Итак, теперь нужно использовать PyTuple_SetItem
(это правильно обрабатывает элементы NULL
и не пытается уменьшить ссылку на NULL-указатель) для установки NULL
элементов в кортеже, прежде чем что-либо можно будет сделать с 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-битных сборок) будет создавать новый объект кортежа и не использовать старый, потому что добавление элемента означает добавление 8 байтов к занимаемой памяти (по крайней мере, для 64-битных сборок), а 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-runtime, и поэтому возможно изменение размера указателя:
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
Теперь старый объект расширяется, а идентификатор сохраняется!
person
ead
schedule
08.12.2019