# Getting the class name of a code/frame object in CPython 3.11 C API

**URL:** <https://discuss.python.org/t/getting-the-class-name-of-a-code-frame-object-in-cpython-3-11-c-api/17396>\
**Category:** Python Help\
**Created:** [July 15, 2022, 2:35pm UTC](https://discuss.python.org/t/getting-the-class-name-of-a-code-frame-object-in-cpython-3-11-c-api/17396 "2022-07-15T14:35:49Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![joerick](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/joerick/32/3719_2.png) [@joerick](https://discuss.python.org/u/joerick)\
**Post date:** [July 15, 2022, 2:35pm UTC](https://discuss.python.org/t/getting-the-class-name-of-a-code-frame-object-in-cpython-3-11-c-api/17396/1 "2022-07-15T14:35:49Z")

</div>

Hello there.

I maintain a library called [pyinstrument](https://github.com/joerick/pyinstrument), a profiler for CPython. It observes a program’s execution and prints output that looks like this-

```nohighlight
  _ ._ __/__ _ _ _ _ _/_ Recorded: 14:42:13 Samples: 30
 /_//_/// /_\ / //_// / //_'/ // Duration: 0.332 CPU time: 0.050
/ _/ v4.2.0

Program: examples/wikipedia_article_word_count.py

0.332 <module> <string>:1
   [9 frames hidden] <string>, runpy, <built-in>
      0.330 _run_code runpy.py:63
      └─ 0.330 <module> wikipedia_article_word_count.py:1
         ├─ 0.281 main wikipedia_article_word_count.py:39
         │ └─ 0.278 download wikipedia_article_word_count.py:15
         │ ├─ 0.274 urlopen urllib/request.py:139
         │ │ [61 frames hidden] urllib, http, socket, ssl, <built-in>...
         │ └─ 0.004 read http/client.py:449
         │ [12 frames hidden] http, socket, ssl, <built-in>
         └─ 0.049 <module> urllib/request.py:1
               [39 frames hidden] urllib, hashlib, <built-in>, http, ss...

```

I’m currently [working on a feature](https://github.com/joerick/pyinstrument/pull/203) that prints the class name of a function, if that function is a method. (e.g. in the above output, it would print `HTTPResponse.read`, rather than just `read`). It does this by looking at the first argument of the frame and, if it’s called ‘self’ or ‘cls’, it gets the type of this object.

That code can be seen here: [pyinstrument/stat\_profile.c at e4a6e0805a30f0de0077534ac87d1cfeee53beb4 · joerick/pyinstrument · GitHub](https://github.com/joerick/pyinstrument/blob/e4a6e0805a30f0de0077534ac87d1cfeee53beb4/pyinstrument/low_level/stat_profile.c#L321) . It uses `code->co_varnames ` and `frame->f_localsplus` to read this information. It’s slightly complicated by the fact that the local might be a ‘cell’ variable, but that’s handled too.

(It’s quite important that the technique used is fast. Keeping the profiler low-overhead is important because otherwise it can distort the data. So performance is a concern.)

I’ve upgraded this branch to CPython 3.11, and I’ve now got a problem. I can’t access these fields any more, as they’ve become private to the interpreter. The release notes [say](https://docs.python.org/3.11/whatsnew/3.11.html#pyframeobject-3-11-hiding) “`f_localsplus`: no public API (renamed to `f_frame.localsplus`)”. `co_varnames` isn’t mentioned in the release notes, but it’s gone from the headers.

The only way I can see to work around this is using the new [`PyFrame_GetLocals`](https://docs.python.org/3.11/c-api/frame.html#c.PyFrame_GetLocals) function. But that would have the side effect of calling ‘fast-to-locals’ on every frame in the program - which is something that I want to avoid as it seems like it could have pretty major performance impacts - adding profiling overhead or just changing how the program performs.

So my question is - is there a better way to get the class name of a code/frame object? The variable named ‘self’/‘cls’ is a bit of a hack anyway. Perhaps there’s a static way that just uses the ‘code’ object? Or, is the ‘fast-to-locals’ thing not as much of an issue as I’m making it?

---

<div class="post-metadata">

**Author:** ![steven.daprano](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steven.daprano/32/1083_2.png) [@steven.daprano](https://discuss.python.org/u/steven.daprano)\
**Post date:** [July 17, 2022, 3:31am UTC](https://discuss.python.org/t/getting-the-class-name-of-a-code-frame-object-in-cpython-3-11-c-api/17396/2 "2022-07-17T03:31:50Z")

</div>

Hi Joe,

First things first: if public fields have suddenly been made private without warning, that is a breaking change that should be reported on [the bug tracker](https://github.com/python/cpython/issues).

As for reporting the qualified name of the method, you should look at the ` __qualname__ ` attribute on the function object:

```python

from http.client import HTTPResponse

print(HTTPResponse.read. __qualname__ )

# --> prints 'HTTPResponse.read'

```

If you have the bound method object instead:

```python

import http.client

connection = http.client.HTTPSConnection("www.python.org")

connection.request("GET", "/")

response = connection.getresponse()

print(response.read. __func__. __qualname__ )

# --> prints 'HTTPResponse.read'

```

Does this help?

---

<div class="post-metadata">

**Author:** ![kj0](https://avatars.discourse-cdn.com/v4/letter/k/db5fbb/32.png) [@kj0](https://discuss.python.org/u/kj0)\
**Post date:** [July 17, 2022, 6:58pm UTC](https://discuss.python.org/t/getting-the-class-name-of-a-code-frame-object-in-cpython-3-11-c-api/17396/3 "2022-07-17T18:58:55Z")

</div>

Regarding the code objects:  
One temporary workaround for any of the code objects is to access them from the Python level. E.g. `PyObject_GetAttrString(code, "co_varnames")`. However, this is slower than accessing via the C API.

Currently there is only a public C API getter for accessing the `co_code` attribute (`PyCode_GetCode`). I’ll try to add function getters to the C API for the other fields before the next 3.11 release. These functions will likely be slower than accessing the struct fields directly in 3.10. In 3.11 a lot of these fields are gone. We still maintain compatibility at the Python level by reconstructing the pre-3.11 fields on the fly. This means you should try to reduce accesses to these fields because they’re now almost always dynamically created.

Regarding the frame objects, I’m not too sure. Maybe @vstinner can help you. Should we add a public C API for those?

---

<div class="post-metadata">

**Author:** ![guido](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/guido/32/21_2.png) [@guido](https://discuss.python.org/u/guido)\
**Post date:** [July 20, 2022, 11:41pm UTC](https://discuss.python.org/t/getting-the-class-name-of-a-code-frame-object-in-cpython-3-11-c-api/17396/4 "2022-07-20T23:41:57Z")

</div>

At the risk of speaking out of turn, @joerick, have you considered including internal headers and call `_PyCode_GetVarnames()` , `_PyCode_GetCellvars` and `_PyCode_GetFreevars` directly?

I agree it’s not pretty, but since you’re writing a profiler you’re clearly already depending on unstable APIs.

---

<div class="post-metadata">

**Author:** ![joerick](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/joerick/32/3719_2.png) [@joerick](https://discuss.python.org/u/joerick)\
**Post date:** [July 21, 2022, 8:46am UTC](https://discuss.python.org/t/getting-the-class-name-of-a-code-frame-object-in-cpython-3-11-c-api/17396/5 "2022-07-21T08:46:31Z")

</div>

Thanks all, this is super helpful! I’ll do some profiling on the ` __qualname__ ` approach to see if that is quick enough to be workable. `PyObject_GetAttrString` looks convenient too.

> I’ll try to add function getters to the C API for the other fields before the next 3.11 release. These functions will likely be slower than accessing the struct fields directly in 3.10. In 3.11 a lot of these fields are gone. We still maintain compatibility at the Python level by reconstructing the pre-3.11 fields on the fly. This means you should try to reduce accesses to these fields because they’re now almost always dynamically created.

Thanks, that would be useful, and might help others who are using these objects from the C API side.

> have you considered including internal headers and call `_PyCode_GetVarnames()` , `_PyCode_GetCellvars` and `_PyCode_GetFreevars` directly?

I had not realised this was possible! So those symbols are still accessible to a C extension, they’re just in an internal header? That’s very cool. I’ll have a play with this. If the above routes are not feasible, dropping to this level sounds like a good backstop 🙂

---

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [August 1, 2022, 11:27am UTC](https://discuss.python.org/t/getting-the-class-name-of-a-code-frame-object-in-cpython-3-11-c-api/17396/6 "2022-08-01T11:27:57Z")

</div>

I proposed adding PyFrame\_GetVar(), but the discussion didn’t go far: [[C API] Add PyFrame\_GetVar(frame, name) function · Issue #91248 · python/cpython · GitHub](https://github.com/python/cpython/issues/91248)

It seems like Mark plans to do something more generic, but I didn’t understand the details.

---

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [August 3, 2022, 8:52pm UTC](https://discuss.python.org/t/getting-the-class-name-of-a-code-frame-object-in-cpython-3-11-c-api/17396/7 "2022-08-03T20:52:40Z")

</div>

See also [gh-94936: C getters: co\_varnames, co\_cellvars, co\_freevars by Fidget-Spinner · Pull Request #95008 · python/cpython · GitHub](https://github.com/python/cpython/pull/95008) : would such API fills your needs?

---

<div class="post-metadata">

**Author:** ![joerick](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/joerick/32/3719_2.png) [@joerick](https://discuss.python.org/u/joerick)\
**Post date:** [August 5, 2022, 9:19am UTC](https://discuss.python.org/t/getting-the-class-name-of-a-code-frame-object-in-cpython-3-11-c-api/17396/8 "2022-08-05T09:19:20Z")

</div>

Thank you for merging the code getters PR. That will help for sure. I haven’t had a chance to fully test it yet, but I think I can use the new PyCode\_GetVarnames getter to check for a `self` varname, and then if it’s there, use PyFrame\_GetLocals and PyDict\_GetItem to find self and get the type with \_PyType\_Name.

That still has the side effect of doing fast-to-locals on a few frames, but at least it’s only the frames that actually have `self` arguments.

PyFrame\_GetVar() would be really nice, yeah! Especially from a performance PoV, if it could avoid all the object creation that the above involves (tuple for the code args and dict for the locals).
