Skip to content

ENH: Expand abstract DType hierarchy - #31755

Merged
mattip merged 12 commits into
numpy:mainfrom
seberg:abstract-dtypes-register
Oct 16, 2026
Merged

mattip merged 12 commits into
numpy:mainfrom
seberg:abstract-dtypes-register

Conversation

@seberg

@seberg seberg commented Jun 25, 2026

Copy link
Copy Markdown
Member

Introduces NumericAbstractDType and a fuller numeric "kind" tree mirroring the array-API hierarchy:

NumericAbstractDType
    IntegerAbstractDType
        SignedIntegerAbstractDType
        UnsignedIntegerAbstractDType
    InexactAbstractDType
        FloatAbstractDType
        ComplexAbstractDType

The new abstract DTypes are exposed on numpy.dtypes and the legacy concrete DTypes inherit from them so isinstance/issubclass reflect the kind hierarchy.

Adds register() on abstract DTypes for legacy/user-defined DTypes to opt into a kind, with custom __subclasscheck__ / __instancecheck__ on the metaclass consulting the registered cache and the NPY_DT_NUMERIC flag.

np.isdtype now uses and accepts DType classes (including these abstracts), although passing a non-instance as first argument is deprecated as I think it is a bit wonky.
(Do allow the scalar types as they map OK, but... really that shouldn't be the normal use-case, I think.)

(I used a bit of AI to implement some larger patterns, and some of the tests are; I'll go through once more myself as well.)

@jorenham

jorenham commented Jun 25, 2026 •

Copy link
Copy Markdown
Member

I don't think it's possible to make this type-safe; because there's no mechanism to "tell" static-type checkers that some registered custom dtype should be assignable to the relevant absract dtype if that custom dtype isn't an actual subclass of it.

This is why using the register method from abc types is often discouraged, and is also why the static types of collections.abc and stdlib subclasses thereof are such a mess today.

The abstract dtypes themselves seem like a good idea to me though, it's just the register and __isinstance__ magic that I'm against.

@jorenham

Copy link
Copy Markdown
Member

Naming-wise, should FloatAbstractDType -> FloatingAbstractDType, and ComplexAbstractDType -> ComplexFloatingAbstractDType, for consistency with np.floating / np.complexfloating? Similarly it would be more consistent to call NumericAbstractDType -> NumberAbstractDType so that it matches np.number I think

@seberg

seberg commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

The abstract dtypes themselves seem like a good idea to me though, it's just the register and isinstance magic that I'm against.

Hmmm, maybe we don't need it strictly, although it is nice to tape over things for old-style dtypes. But... I hope to move ml_dtypes so maybe that doesn't actually matter too much if you think register is too super annoying.

One nice thing about registering (in the future) is that it allows easy creation for custom "superclasses" that also match NumPy if you want to and that can be very nice for promotion logic (i.e. to spell out Int64DType|MyDType). But... maybe we can spell that in a different way nicely, and then I am not sure it matters too much to allow .register().
(I somewhat like it to be the same as the abc, but I guess ABC predate typing, etc...)

ComplexFloatingAbstractDType

Yeah, I would prefer that, we just already ahve it in C... Maybe we can just add an alias there for now...

@seberg seberg changed the title ENH: Expand abstract DType hierarchy and allow register() ENH: Expand abstract DType hierarchy Jul 18, 2026
@seberg
seberg force-pushed the abstract-dtypes-register branch from dec3b0b to aab2f99 Compare July 18, 2026 09:53
@seberg

seberg commented Jul 18, 2026

Copy link
Copy Markdown
Member Author

Updated this and renamed the Python side for now only. .register() is removed but for now the Numeric flag is special-cased for subclasscheck (i.e. quaddtype uses it).

(So maybe I am not 100% sure if there isn't some follow-up, but I think this should be ready -- although I should probably go through once more myself due to that back and forth.)

@jorenham jorenham 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.

@jorenham

Copy link
Copy Markdown
Member

don't forget about the numpy.dtypes stubs :) 7e1f944/numpy/dtypes.pyi

(or, if you prefer, I could also write them)

@seberg

seberg commented Sep 14, 2026

Copy link
Copy Markdown
Member Author

Sorry that this was not updated for a while. @jorenham I now pushed an update, the types are pretty much added by claude, they seemed reasonable, but can you take a look?

I changed things now to:

  • Adopted your names, it makes sense to stick to the old ones in Python... (without even thinking much about what is nicer...)
  • Rip out register as much as I like it, I guess we should be able to get away without it mostly. This simplifies things a lot of course.
  • For now I removed the fast-path to check the flag
  • There is a fun thing where our _is_numeric flag is true for booleans but it's excluded from NumberAbstractDType. We may want to move away from that at some point.

Introduces ``NumericAbstractDType`` and a fuller numeric "kind" tree
mirroring the array-API hierarchy:

    NumericAbstractDType
        IntegerAbstractDType
            SignedIntegerAbstractDType
            UnsignedIntegerAbstractDType
        InexactAbstractDType
            FloatAbstractDType
            ComplexAbstractDType

The new abstract DTypes are exposed on ``numpy.dtypes`` and the legacy
concrete DTypes inherit from them so ``isinstance``/``issubclass`` reflect
the kind hierarchy.

Adds ``register()`` on abstract DTypes for legacy/user-defined DTypes to
opt into a kind, with custom ``__subclasscheck__`` / ``__instancecheck__``
on the metaclass consulting the registered cache and the
``NPY_DT_NUMERIC`` flag.

``np.isdtype`` now uses and accepts DType classes (including these abstracts),
although passing a non-instance as first argument is deprecated as I think
it is a bit wonky.
(Do allow the scalar types as they map OK, but... really that shouldn't be
the normal use-case, I think.)
@seberg
seberg force-pushed the abstract-dtypes-register branch from 7a65561 to 193c506 Compare September 14, 2026 16:45
@jorenham
jorenham self-requested a review September 14, 2026 17:54

@jorenham jorenham 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.

Looks a lot better now; nice.

I left some typing-related comments, most of them about stuff that can be removed.

Comment thread numpy/dtypes.pyi Resolved
Comment thread numpy/dtypes.pyi Resolved
Comment thread numpy/dtypes.pyi Resolved
Comment thread numpy/dtypes.pyi Resolved
Comment thread numpy/typing/tests/data/pass/dtype.py Resolved
@jorenham
jorenham self-requested a review October 7, 2026 18:52

@jorenham jorenham 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.

Typing changes look good now, thanks!

@mattip

mattip commented Oct 11, 2026

Copy link
Copy Markdown
Member

LGTM. This needs a release note

@seberg

seberg commented Oct 15, 2026

Copy link
Copy Markdown
Member Author

Sorry, finally added a brief release note (and resolved merge conflict).

@mattip

mattip commented Oct 15, 2026

Copy link
Copy Markdown
Member

The doctest failures are due to this PR.

Comment thread numpy/_core/numerictypes.py Resolved
@mattip
mattip merged commit d96ccfe into numpy:main Oct 16, 2026
93 checks passed
@mattip

mattip commented Oct 16, 2026

Copy link
Copy Markdown
Member

Thanks @seberg

@seberg
seberg deleted the abstract-dtypes-register branch October 16, 2026 10:49
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