-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
[C API] Get "self" args or non-null co_varnames from frame object with C-API #90324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hello, I am a maintainer with the PyBind11 project. We have been following the 3.11 development branch and have noticed an issue we are encountering with changes to the C-API. Particularly, we have an edge case in our overloading dispatch mechanism that we used to solve by inspecting the "self" argument in the co_varnames member of the python frame object: (https://github.com/pybind/pybind11/blob/a224d0cca5f1752acfcdad8e37369e4cda42259e/include/pybind11/pybind11.h#L2380). However, in the new struct, the co_varnames object can now be null. There also doesn't appear to be any public API to populate it on the C-API side. Accessing it via the "inspect" module still works, but that requires us to run a Python code snippit in a potentially very hot code path: (https://github.com/pybind/pybind11/blob/a224d0cca5f1752acfcdad8e37369e4cda42259e/include/pybind11/pybind11.h#L2408). As such, we were hoping that either there is some new API change we have missed, or if there is some way other modern (and hopefully somewhat stable way to access the API) so we can emulate the old behavior with the C-API. |
Pablo, Mark: I am guessing that at least one of you know about this. |
It would be great to get this looked at before things start getting too locked in for 3.11! |
Yes, we should expose the tuple of variable names, both in Python and in the C-API. Would something like In the meantime, since you were reading OOI, how do you cope with non-local self? |
In addition to what Mark said, note that co_varnames get's populated lazily by the Python-level getter for code.co_varnames. So could you call the Python function before entering the hot path? Regardless, a dedicated C-API for this like Mark suggested would be the better solution. |
We didn't want to read colocalsplus directly because we were worried about the stability of that approach and the code complexity / readability. Also, I wasn't aware that colocalsplus would work or if that was lazily populated as well. The functions used in CPython to extract the args from colocalsplus do not seem to be public and would need to be reimplemented by PyBind11, right? That seems very brittle as try to support future Python versions and may break in the future. Having a somewhat stable C-API to query this information seems like it would be the best solution, but I am open to suggestions on how to best proceed. How would you all recommend PyBind11 proceed with supporting 3.11 if not a C-API addition? The PyBind11 authors want to resolve this before the API becomes too locked down for 3.11. |
OOI, how do you cope with non-local self?
|
It would be nice to have a PyFrame_GetVariable(frame, "self") function: get the value of the "frame" variable of the specified frame object. |
I saw the latest Python 3.11 5A release notes on the frame API changes. Do the notes mean the only officially supported way of accessing co_varnames is now through the Python interface and the inspect module? By using PyObject_GetAttrString? Also, the documentation in the WhatsNew is a bit unclear as PyObject_GetAttrString(frame, "f_locals") doesn't work for PyFrameObject*, only PyObject* and it doesn't describe how to get the PyObject* version of FrameObject. The same problem also happens when trying to access the co_varnames field of the PyCodeObject*. |
Oh, would you mind to elaborate? Example in Python: $ ./python
Python 3.11.0a5+
>>> import sys
>>> f=sys._getframe()
>>> f.f_locals
{'__name__': '__main__', '__doc__': None, ...} |
The frame object I am referring to was: PyFrameObject *frame = PyThreadState_GetFrame(PyThreadState_Get()); This frame can not be used with PyObject_GetAttrString. Is there anyway to get the PyObject* associated with a PyFrameObject*? It seems weird that some functionality is just not accessible using the Stable ABI of PyThreadState_GetFrame . To elabroate: I was referring to the migration guide in the changelog btw:
I tried importing sys._getframe(), but that gave an attribute error interestingly enough. Run a full code snippit here works: https://github.com/pybind/pybind11/blob/96b943be1d39958661047eadac506745ba92b2bc/include/pybind11/pybind11.h#L2429, but is really slow and we would like avoid having to rely on it. Not to mention relying on a function that is an starts with an underscore seems like it really should be avoided. |
Example of C code that I added to _testcapi: static PyObject *
get_caller_locals(PyObject *self, PyObject *Py_UNUSED(args))
{
PyFrameObject *frame = PyThreadState_GetFrame(PyThreadState_Get());
if (frame == NULL) {
Py_RETURN_NONE;
}
return PyObject_GetAttrString(frame, "f_locals");
} Python example: import _testcapi
def f():
x = 1
y = 2
print(_testcapi.get_caller_locals())
f() Output on Python 3.11: => it just works. A PyFrameObject is a regular Python object, you can use functions like PyObject_GetAttrString(). Maybe I missed something, correct me if I'm wrong. |
Ah. I see. If you pass a PyFrameObject* frame to PyObject_GetAttrString(), you get a compiler warning. You should cast it explicitly: PyObject_GetAttrString((PyObject*)frame, "f_locals"). |
I proposed #75381 to fix the compiler warnings in the doc. |
Python 3.12 added PyFrame_GetVar() and PyFrame_GetVarString(). You can use https://pythoncapi-compat.readthedocs.io/ to get these functions on older Python versions. I gave examples on how to get the Python now has a wider C API to access PyFrameObject members: https://docs.python.org/dev/c-api/frame.html Finally, https://docs.python.org/dev/whatsnew/3.11.html#whatsnew311-c-api-porting documents how to replace removed PyFrameObject members with function calls. I close the issue. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: