8000 Fix `__module__` attribute for shared types by da-woods · Pull Request #6845 · cython/cython · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fix __module__ attribute for shared types #6845

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

Closed
wants to merge 12 commits into from

Conversation

da-woods
Copy link
Contributor
@da-woods da-woods commented May 6, 2025

It can't be a descriptor because that messes up
type(func).__module__. I decided not to put it in __dict__ because that involves instantiating __dict__ for every instance (which we've previously decided not to do) and also deciding on the behaviour when __dict__ is replaced.

Therefore the solution seems to be to add some special casing to tp_getattro and tp_setattro.

Fixes #6841.

@@ -1771,7 +1771,7 @@ static PyObject *__Pyx_CoroutineAwait_no_new(PyTypeObject *type, PyObject *args,
// We are applying this to all Python versions (hence the commented out version guard)
// to make the behaviour explicit.
// #if CYTHON_USE_TYPE_SPECS
static PyObject *__Pyx_CoroutineAwait_reduce_ex(__pyx_CoroutineAwaitObject *self, PyObject *arg) {
static PyObject *__Pyx_CoroutineTypes_reduce_ex(PyObject *self, PyObject *arg) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reasons I don't understand, changing the definition of __module__ magically made coroutines pickleable under protocols 1 and 2. Therefore I just explicitly banned it using the same function as CoroutineAwait uses.

It can't be a descriptor because that messes up
`type(func).__module__`. I decided not to put it in `__dict__`
because that involves instantiating `__dict__` for every instance
(which we've previously decided not to do) and also deciding on
the behaviour when `__dict__` is replaced.

Therefore the solution seems to be to add some special casing
to tp_getattro and tp_setattro.

Fixes cython#6841.
"""
>>> test_module.__module__
'cyfunction'
>>> type(test_module).__module__.startswith("_cython")
8000 Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a bit fragile locally, because for some reason I always end up with an older cached cyfunction implementation.

It works when I manually import the module alone but not when I use runtests. I'm not sure what the CI will end up doing, but I'm fairly sure this is a caching problem and will go away when the shared lib version changes.

@scoder
Copy link
Contributor
scoder commented May 7, 2025 via email

@da-woods
Copy link
Contributor Author
da-woods commented May 7, 2025

I'm always wary of changing attribute access away from PyObject_GenericGetAttr due to CPython's internal optimisations.

Agree

the pickle changes seem risky.

I don't think it's as bad as it seems - the non-pickleability of coroutines

def test_pickle(self):
is already tested so it's only making that more explicit.

Definitely not a change to merge at this point,

Yes. Probably worth waiting to see how many people the bug really affects for now

Copy link
Contributor
@scoder scoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine, just want to make sure we're not introducing performance regressions.

@@ -1771,7 +1771,7 @@ static PyObject *__Pyx_CoroutineAwait_no_new(PyTypeObject *type, PyObject *args,
// We are applying this to all Python versions (hence the commented out version guard)
// to make the behaviour explicit.
// #if CYTHON_USE_TYPE_SPECS
static PyObject *__Pyx_CoroutineAwait_reduce_ex(__pyx_CoroutineAwaitObject *self, PyObject *arg) {
static PyObject *__Pyx_CoroutineTypes_reduce_ex(PyObject *self, PyObject *arg) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we're renaming this function, I think it's worth stating in the function name that this is not a pickle implementation but something that intentionally prevents pickling.

Suggested change
static PyObject *__Pyx_CoroutineTypes_reduce_ex(PyObject *self, PyObject *arg) {
static PyObject *__Pyx_Coroutine_fail_reduce_ex(PyObject *self, PyObject *arg) {

Comment on lines -1884 to +1910
{Py_tp_getattro, (void *) PyObject_GenericGetAttr},
{Py_tp_getattro, (void *)__pyx_Coroutine_getattro},
{Py_tp_setattro, (void *)__pyx_Coroutine_setattro},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you run a quick micro-benchmark to see if this has any visible impact on the time it takes to look up and call .send() from Python code (e.g. from within asyncio)? Especially with all the attribute lookup optimisations in Py3.1[34]? Just so that we have a number.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do (although it'll probably have to wait a few days).

There's also a Limited API 3.8 breakage that needs fixing for this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just calling .send() on a generator looks like it ends up about 3x slower. That seems fairly bad so I'm going to try to implement an alternative approach instead.

@da-woods
Copy link
Contributor Author

I've proposed an alternative version with metaclasses which hopefully avoids the performance cost of this (at the cost of a little more code).

@da-woods
Copy link
Contributor Author
da-woods commented Jun 1, 2025

I still think #6882 is better, but this is working as well as it can be

I think they're only getting enabled because doctest can now see
the functions.
da-woods added a commit that referenced this pull request Jun 7, 2025
This seems the better way to do it because it doesn't impose any runtime
cost to attribute lookup. It makes all of the shared types that have a
`__module__` attribute inherit from a metaclass that reports the module
correctly.

Supercedes #6845; fixes
#6841
da-woods added a commit to da-woods/cython that referenced this pull request Jun 7, 2025
This seems the better way to do it because it doesn't impose any runtime
cost to attribute lookup. It makes all of the shared types that have a
`__module__` attribute inherit from a metaclass that reports the module
correctly.

Supercedes cython#6845; fixes
cython#6841
@da-woods da-woods added this to the 3.1.2 milestone Jun 7, 2025
@da-woods da-woods closed this Jun 7, 2025
@da-woods da-woods deleted the module branch June 7, 2025 13:47
@da-woods da-woods removed this from the 3.1.2 milestone Jun 7, 2025
scoder pushed a commit that referenced this pull request Jun 8, 2025
This seems the better way to do it because it doesn't impose any runtime
cost to attribute lookup. It makes all of the shared types that have a
`__module__` attribute inherit from a metaclass that reports the module
correctly.

Supercedes #6845; fixes
#6841
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] Invalid member_descriptor as __module__ on type(cython function) under 3.1.0rc1
2 participants
0