Hi all,
I am trying to figure a difference in behavior between Python 3.10 and 3.11 when it comes to using PyEval_EvalCodeEx. My use case is a bit exotic, I need to call a function but control the locals passed to the function when called. I have the following helper that works fine on Python 3.10 but fails to pass a default argument on 3.11. That helper does not support kwonly arguments but that is unrelated. I tried comparing the implementation of PyEval_EvalCodeEx in 3.10 and 3.11 but did not find anything I believe relevant. For reference the project making use of this is GitHub - nucleic/enaml: Declarative User Interfaces for Python, the helper is defined in enaml/src/funchelper.c and used in enaml/src/declarative_function.cpp . Calling my test function directly succeed but using the wrapper it complains about a missing argument.
Any idea is welcome
Matthieu
PyObject*
call_func( PyObject* mod, PyObject* args )
{
PyObject* func;
PyObject* func_args;
PyObject* func_kwargs;
PyObject* func_locals = Py_None;
if( !PyArg_UnpackTuple( args, "call_func", 3, 4, &func, &func_args, &func_kwargs, &func_locals ) )
{
return 0;
}
if( !PyFunction_Check( func ) )
{
PyErr_SetString( PyExc_TypeError, "function must be a Python function" );
return 0;
}
if( !PyTuple_Check( func_args ) )
{
PyErr_SetString( PyExc_TypeError, "arguments must be a tuple" );
return 0;
}
if( !PyDict_Check( func_kwargs ) )
{
PyErr_SetString( PyExc_TypeError, "keywords must be a dict" );
return 0;
}
if( func_locals != Py_None && !PyMapping_Check( func_locals ) )
{
PyErr_SetString( PyExc_TypeError, "locals must be a mapping" );
return 0;
}
if( func_locals == Py_None )
func_locals = 0;
PyObject** defaults = 0;
Py_ssize_t num_defaults = 0;
PyObject* argdefs = PyFunction_GET_DEFAULTS( func );
if( ( argdefs ) && PyTuple_Check( argdefs ) )
{
PyObject_Print(argdefs, stdout, 0);
defaults = &PyTuple_GET_ITEM( reinterpret_cast<PyTupleObject*>( argdefs ), 0 );
num_defaults = PyTuple_Size( argdefs );
}
PyObject** keywords = 0;
Py_ssize_t num_keywords = PyDict_Size( func_kwargs );
if( num_keywords > 0 )
{
keywords = PyMem_NEW( PyObject*, 2 * num_keywords );
if( !keywords )
{
PyErr_NoMemory();
return 0;
}
Py_ssize_t i = 0;
Py_ssize_t pos = 0;
while( PyDict_Next( func_kwargs, &pos, &keywords[ i ], &keywords[ i + 1 ] ) )
i += 2;
num_keywords = i / 2;
/* XXX This is broken if the caller deletes dict items! */
}
// XXX Support kwonly defaults
printf("Arg number: %d, keyword number %d, default number %d\n", PyTuple_Size( func_args ), num_keywords, num_defaults);
PyObject* result = PyEval_EvalCodeEx(
PyFunction_GET_CODE( func ),
PyFunction_GET_GLOBALS( func ),
func_locals,
&PyTuple_GET_ITEM( func_args, 0 ),
PyTuple_Size( func_args ),
keywords, num_keywords,
defaults, num_defaults,
NULL,
PyFunction_GET_CLOSURE( func )
);
if( keywords )
PyMem_DEL( keywords );
return result;
}