PyDict_SetItem 是否增加了键的引用计数,如果是,它发生在代码的哪个位置?
Does PyDict_SetItem increase the reference count of the key, and if so, where in the code does it take place?
TLDR:PyDict_SetItem
递增键和值,但这发生在代码的什么地方?
PyDict_SetItem 调用 insertdict
.
insertdict immediately performs Py_INCREF
on both the key and the value. However, at the end of the success path,然后对键(但不是值)执行 Py_DECREF
。这段代码中一定有我遗漏的部分,它在执行此 PY_DECREF
之前在密钥上执行了额外的 PY_INCREF
。 我的问题是这个额外的 PY_INCREF 在哪里以及为什么发生? 为什么 insertdict
开头的初始 Py_INCREF
不够?
由此看来,PyDict_SetItem
只是增加了value的引用计数,并没有增加key。当然,这不是真的。例如,在 PyDict_SetItemString 中,它采用 char *
,通过 PyUnicode_FromString
(其中 returns 一个新值)将其转换为 PyObject,执行 Py_DECREF
调用 PyDict_SetItem
后的新值。如果 PyDict_SetItem
不增加密钥,而 PyDict_SetItemString
减少刚刚创建的密钥,程序最终可能会出现段错误。鉴于那没有发生,看来我在这里遗漏了一些东西。
最后,这段代码应该证明 PyDict_SetItem
增加了键和值,并且调用者应该减少键和值,除非它们是借来的引用/或者将键和值给别人。
#include <Python.h>
#include <stdio.h>
int main(void)
{
Py_Initialize();
PyObject *dict = NULL, *key = NULL, *value = NULL;
int i = 5000;
char *o = "foo";
if (!(dict = PyDict_New())) {
goto error;
}
if (!(key = Py_BuildValue("i", i))) {
goto error;
}
if (!(value = Py_BuildValue("s", o))) {
goto error;
}
printf("Before PyDict_SetItem\n");
printf("key is %i\n", key->ob_refcnt); /* Prints 1 */
printf("value is %i\n", value->ob_refcnt); /* Prints 1 */
printf("Calling PyDict_SetItem\n");
if (PyDict_SetItem(dict, key, value) < 0) {
goto error;
}
printf("key is %i\n", key->ob_refcnt); /* Prints 2 */
printf("value is %i\n", value->ob_refcnt); /* Prints 2 */
printf("Decrefing key and value\n");
Py_DECREF(key);
Py_DECREF(value);
printf("key is %i\n", key->ob_refcnt); /* Prints 1 */
printf("value is %i\n", value->ob_refcnt); /* Prints 1 */
Py_Finalize();
return 0; // would return the dict in normal code
error:
printf("error");
Py_XDECREF(dict);
Py_XDECREF(key);
Py_XDECREF(value);
Py_Finalize();
return 1;
}
你可以这样编译:
gcc -c -I/path/to/python/include/python3.7m dict.c
gcc dict.o -L/path/to/python/lib/python3.7/config-3.7m-i386-linux-gnu -L/path/to/python/lib -Wl,-rpath=/path/to/python/lib -lpython3.7m -lpthread -lm -ldl -lutil -lrt -Xlinker -export-dynamic -m32
insertdict
中的 Py_DECREF(key);
并非所有成功都会发生。它发生在成功时当一个相等的键已经存在时,要么是因为有一个现有的条目,要么是因为该字典是一个拆分-table 字典与其他字典共享密钥有那把钥匙。在该路径上,未插入提供的密钥,因此需要取消原始 Py_INCREF(key);
。
在密钥不存在的路径上,insertdict
命中了一个 different return
statement 并且没有减少密钥:
if (ix == DKIX_EMPTY) {
/* Insert into new slot. */
assert(old_value == NULL);
if (mp->ma_keys->dk_usable <= 0) {
/* Need to resize. */
if (insertion_resize(mp) < 0)
goto Fail;
}
Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);
ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries];
dictkeys_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries);
ep->me_key = key;
ep->me_hash = hash;
if (mp->ma_values) {
assert (mp->ma_values[mp->ma_keys->dk_nentries] == NULL);
mp->ma_values[mp->ma_keys->dk_nentries] = value;
}
else {
ep->me_value = value;
}
mp->ma_used++;
mp->ma_version_tag = DICT_NEXT_VERSION();
mp->ma_keys->dk_usable--;
mp->ma_keys->dk_nentries++;
assert(mp->ma_keys->dk_usable >= 0);
ASSERT_CONSISTENT(mp);
return 0;
}
TLDR:PyDict_SetItem
递增键和值,但这发生在代码的什么地方?
PyDict_SetItem 调用 insertdict
.
insertdict immediately performs Py_INCREF
on both the key and the value. However, at the end of the success path,然后对键(但不是值)执行 Py_DECREF
。这段代码中一定有我遗漏的部分,它在执行此 PY_DECREF
之前在密钥上执行了额外的 PY_INCREF
。 我的问题是这个额外的 PY_INCREF 在哪里以及为什么发生? 为什么 insertdict
开头的初始 Py_INCREF
不够?
由此看来,PyDict_SetItem
只是增加了value的引用计数,并没有增加key。当然,这不是真的。例如,在 PyDict_SetItemString 中,它采用 char *
,通过 PyUnicode_FromString
(其中 returns 一个新值)将其转换为 PyObject,执行 Py_DECREF
调用 PyDict_SetItem
后的新值。如果 PyDict_SetItem
不增加密钥,而 PyDict_SetItemString
减少刚刚创建的密钥,程序最终可能会出现段错误。鉴于那没有发生,看来我在这里遗漏了一些东西。
最后,这段代码应该证明 PyDict_SetItem
增加了键和值,并且调用者应该减少键和值,除非它们是借来的引用/或者将键和值给别人。
#include <Python.h>
#include <stdio.h>
int main(void)
{
Py_Initialize();
PyObject *dict = NULL, *key = NULL, *value = NULL;
int i = 5000;
char *o = "foo";
if (!(dict = PyDict_New())) {
goto error;
}
if (!(key = Py_BuildValue("i", i))) {
goto error;
}
if (!(value = Py_BuildValue("s", o))) {
goto error;
}
printf("Before PyDict_SetItem\n");
printf("key is %i\n", key->ob_refcnt); /* Prints 1 */
printf("value is %i\n", value->ob_refcnt); /* Prints 1 */
printf("Calling PyDict_SetItem\n");
if (PyDict_SetItem(dict, key, value) < 0) {
goto error;
}
printf("key is %i\n", key->ob_refcnt); /* Prints 2 */
printf("value is %i\n", value->ob_refcnt); /* Prints 2 */
printf("Decrefing key and value\n");
Py_DECREF(key);
Py_DECREF(value);
printf("key is %i\n", key->ob_refcnt); /* Prints 1 */
printf("value is %i\n", value->ob_refcnt); /* Prints 1 */
Py_Finalize();
return 0; // would return the dict in normal code
error:
printf("error");
Py_XDECREF(dict);
Py_XDECREF(key);
Py_XDECREF(value);
Py_Finalize();
return 1;
}
你可以这样编译:
gcc -c -I/path/to/python/include/python3.7m dict.c
gcc dict.o -L/path/to/python/lib/python3.7/config-3.7m-i386-linux-gnu -L/path/to/python/lib -Wl,-rpath=/path/to/python/lib -lpython3.7m -lpthread -lm -ldl -lutil -lrt -Xlinker -export-dynamic -m32
insertdict
中的 Py_DECREF(key);
并非所有成功都会发生。它发生在成功时当一个相等的键已经存在时,要么是因为有一个现有的条目,要么是因为该字典是一个拆分-table 字典与其他字典共享密钥有那把钥匙。在该路径上,未插入提供的密钥,因此需要取消原始 Py_INCREF(key);
。
在密钥不存在的路径上,insertdict
命中了一个 different return
statement 并且没有减少密钥:
if (ix == DKIX_EMPTY) {
/* Insert into new slot. */
assert(old_value == NULL);
if (mp->ma_keys->dk_usable <= 0) {
/* Need to resize. */
if (insertion_resize(mp) < 0)
goto Fail;
}
Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);
ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries];
dictkeys_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries);
ep->me_key = key;
ep->me_hash = hash;
if (mp->ma_values) {
assert (mp->ma_values[mp->ma_keys->dk_nentries] == NULL);
mp->ma_values[mp->ma_keys->dk_nentries] = value;
}
else {
ep->me_value = value;
}
mp->ma_used++;
mp->ma_version_tag = DICT_NEXT_VERSION();
mp->ma_keys->dk_usable--;
mp->ma_keys->dk_nentries++;
assert(mp->ma_keys->dk_usable >= 0);
ASSERT_CONSISTENT(mp);
return 0;
}