Skip to content

ENH: Port _rational_tests to the Limited API using heap types - #32174

Merged
ngoldbaum merged 11 commits into
numpy:mainfrom
prathamhole14:rational_tests_pylimited
Aug 16, 2026
Merged

ngoldbaum merged 11 commits into
numpy:mainfrom
prathamhole14:rational_tests_pylimited

Conversation

@prathamhole14

@prathamhole14 prathamhole14 commented Aug 3, 2026 •

Copy link
Copy Markdown
Member

PR summary

Related to: #31913, #32451 and #32747
I will make the PR more verbose soon. I want to see if CI is okay with this.
Benchmark report: https://gist.github.com/prathamhole14/17d187d84837de39d190d7a7db0e0d03

AI Disclosure

In understanding things, and finding places where changes are required.

@prathamhole14 prathamhole14 changed the title ENH: Port _rational_tests to the Limited API using heap types [WIP] ENH: Port _rational_tests to the Limited API using heap types Aug 4, 2026
@prathamhole14

Copy link
Copy Markdown
Member Author

This PR is ready for review!

@prathamhole14 prathamhole14 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No tp_traverse because a rational holds two ints and no PyObject*, so it cannot be part of a cycle. Heap types normally want both, but that only leaks at interpreter teardown.

@prathamhole14

Copy link
Copy Markdown
Member Author

I think the failing test is unrelated, this PR is ready for review!

@prathamhole14
prathamhole14 marked this pull request as ready for review August 6, 2026 09:37
@ngoldbaum

Copy link
Copy Markdown
Member

This one is a bit more complicated and needs some C API knowledge because it does some migrating to heap types. @kumaraditya303, would you mind giving it a look?

Comment thread numpy/_core/src/umath/_rational_tests.c Resolved
@kumaraditya303

Copy link
Copy Markdown
Contributor

A few points for heap types:

  • Heap types should always be GC types in general, this is because the following cycle is possible instance -> type -> module -> instance.
  • It is good practise to call tp_slots instead of function directly like tp_alloc instead of PyType_GenericAlloc.
  • Heap types are mutable by default, so if that is unwanted use IMMUTABLE flag.

@prathamhole14

Copy link
Copy Markdown
Member Author

Thanks for the reviews @kumaraditya303!

One thing worth flagging is I had to set tp_alloc explicitly in the spec. It's inherited unconditionally from np.generic, whose allocator is not GC-aware, so without it you allocate non-GC memory that the dealloc then GC-frees. tp_free sorts itself out and CPython will pick up PyObject_GC_Del from object.

And you were right about the flag check. Once these are GC types it's always true, so the branch is gone now.

On point 3: agreed this is a regression the static type used to reject attribute injection. But IMMUTABLETYPE breaks init here because dtype is set at runtime with PyObject_SetAttrString, and PyType_GetDict isn't in the limited API so I can't seed the type dict directly. Any preference on how to handle that, or leave it mutable for now?

@kumaraditya303

Copy link
Copy Markdown
Contributor

agreed this is a regression the static type used to reject attribute injection. But IMMUTABLETYPE breaks init here because dtype is set at runtime with PyObject_SetAttrString, and PyType_GetDict isn't in the limited API so I can't seed the type dict directly. Any preference on how to handle that, or leave it mutable for now?

You can PyType_Freeze although IIRC that was only added in 3.14 so you can skip this if you want.

Comment thread numpy/_core/src/umath/_rational_tests.c Resolved
Comment thread numpy/_core/src/umath/_rational_tests.c Resolved
@prathamhole14
prathamhole14 force-pushed the rational_tests_pylimited branch from 0a24d2b to 949da72 Compare August 14, 2026 12:23
Comment thread numpy/_core/src/umath/_rational_tests.c Resolved
@prathamhole14
prathamhole14 force-pushed the rational_tests_pylimited branch from 949da72 to 255b236 Compare August 15, 2026 08:32

@kumaraditya303 kumaraditya303 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@ngoldbaum ngoldbaum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about a short test like this to make sure the heap type setup is working correctly?

@pytest.mark.thread_unsafe(reason="Relies on global gc state")
def test_rational_subclass_lifetime():
    class R(rational):
        pass

    r = R(1, 2)
    assert r == Rational(1, 2)
    assert gc.is_tracked(r)

    wr = weakref.Ref(R)
    del R
    gc.collect()
    # the instance keeps a reference to its type
    assert wr() is not None

    del r
    gc.collect()
    assert wr() is None

Comment thread numpy/_core/src/umath/_rational_tests.c Resolved
@ngoldbaum

Copy link
Copy Markdown
Member

Thanks @prathamhole14!

@ngoldbaum
ngoldbaum merged commit c452636 into numpy:main Aug 16, 2026
91 checks passed
@prathamhole14
prathamhole14 deleted the rational_tests_pylimited branch August 16, 2026 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants