python - Reference count of function arguments in CPython 3.6 -
in order find out if parameter passed function "temporary" (only passed function) or referenced outside use py_refcnt
. done in c extension package, easier reproducibility decided provide cython implementation based on ipython magic here.
it seems changed functions accept multiple arguments (it still works expected functions take 1 argument) between cpython 3.5 , cpython 3.6:
in [1]: %load_ext cython in [2]: %%cython ...: cdef extern "python.h": ...: py_ssize_t py_refcnt(object o) ...: ...: cpdef func(o, p): ...: return py_refcnt(o)
when run code on 3.5 gives me, expected result:
>>> import numpy np >>> func(np.ones(3), np.ones(3)) 1
but 3.6 gives me 2
:
>>> import numpy np >>> func(np.ones(3), np.ones(3)) 2
in comments asked c code here is:
static pyobject * getrefcount(pyobject *m, pyobject *args) { if (pytuple_checkexact(args) && pytuple_size(args) > 0) { py_ssize_t reference_count = py_refcnt(pytuple_get_item(args, 0)); return pylong_fromssize_t(reference_count); } pyerr_setstring(pyexc_typeerror, "wrong input"); return null; }
and method definition:
{"getrefcount", /* ml_name */ (pycfunction)getrefcount, /* ml_meth */ meth_varargs, /* ml_flags */ "" /* ml_doc */ },
the results same:
>>> import numpy np >>> getrefcount(np.ones(3)) # 3.5 1 >>> getrefcount(np.ones(3)) # 3.6 2
i know (and why) reference count incremented in 3.6. have looked through cpython source code / python issue tracker couldn't find answer.
on python 3.5, arguments happen cleared caller's stack time function executed. on python 3.6, arguments happen still on caller's stack in function's argument tuple.
on python 3.5, function call goes through here:
else { pyobject *callargs; callargs = load_args(pp_stack, na); if (callargs != null) { read_timestamp(*pintr0); c_trace(x, pycfunction_call(func,callargs,null)); read_timestamp(*pintr1); py_xdecref(callargs); } else { x = null; } }
which removes arguments stack build argument tuple:
static pyobject * load_args(pyobject ***pp_stack, int na) { pyobject *args = pytuple_new(na); pyobject *w; if (args == null) return null; while (--na >= 0) { w = ext_pop(*pp_stack); pytuple_set_item(args, na, w); } return args; }
on 3.6, function call goes through here:
if (pycfunction_check(func)) { pythreadstate *tstate = pythreadstate_get(); pcall(pcall_cfunction); stack = (*pp_stack) - nargs - nkwargs; c_trace(x, _pycfunction_fastcallkeywords(func, stack, nargs, kwnames)); }
which goes through here
pyobject * _pycfunction_fastcallkeywords(pyobject *func, pyobject **stack, py_ssize_t nargs, pyobject *kwnames) { ... result = _pycfunction_fastcalldict(func, stack, nargs, kwdict); py_xdecref(kwdict); return result; }
which goes through here:
case meth_varargs: case meth_varargs | meth_keywords: { /* slow-path: create temporary tuple */ ... tuple = _pystack_astuple(args, nargs); ... }
which goes through here:
for (i=0; < nargs; i++) { pyobject *item = stack[i]; py_incref(item); pytuple_set_item(args, i, item); }
which leaves arguments on stack , builds tuple new references arguments.
Comments
Post a Comment