MAINT,API: Introduce __numpy_dtype__ and fix dtype attribute recursion by seberg · Pull Request #30179 · numpy/numpy · GitHub
Skip to content

MAINT,API: Introduce __numpy_dtype__ and fix dtype attribute recursion - #30179

Merged
charris merged 3 commits into
numpy:mainfrom
seberg:__numpy_dtype__
Nov 27, 2025
Merged

charris merged 3 commits into
numpy:mainfrom
seberg:__numpy_dtype__

Conversation

@seberg

@seberg seberg commented Nov 8, 2025

Copy link
Copy Markdown
Member

This is a lot more complex then I expected. We, correctly, deprecated .dtype attribute lookup recursion, however...
The code still had a try/except that still recursed and that try except carried meaning at least in a weird path of:

class mydtype(np.void):
    dtype = ...

Now does that make sense? Maybe not... we also fall back to object in some paths which should have been broken when a dtype attribute existed on a type but it is a property/descriptor.

So, this removes the recursion, but adds a check for __get__ to filter out those cases, this is something we successfully did for other protocols __array__, __array_interface__, etc.

This is a lot more complex then I expected. We, correctly, deprecated
`.dtype` attribute lookup recursion, however...
The code still had a `try/except` that still recursed and that try
except carried meaning at least in a weird path of:
```
class mydtype(np.void):
    dtype = ...
```

Now does that make sense? Maybe not... we also fall back to object
in some paths which should have been broken when a dtype attribute
existed on a type but it is a property/descriptor.

So, this removes the recursion, but adds a check for `__get__` to
filter out those cases, this is something we successfully did for
other protocols `__array__`, `__array_interface__`, etc.

Signed-off-by: Sebastian Berg <sebastianb@nvidia.com>
@seberg seberg added 03 - Maintenance 30 - API and removed 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes labels Nov 8, 2025
@mattip

mattip commented Nov 8, 2025

Copy link
Copy Markdown
Member

@seberg

seberg commented Nov 9, 2025

Copy link
Copy Markdown
Member Author

Yeah, that is a nice thought, we should maybe add it to deprecate the use of the dtype on the instance.
I think there are two things that make me not jump at it. First, we need type lookup for some things anyway (i.e. I would prefer not to require this for user DTypes; that said such user dtypes don'tsupport the conversion right nowI suspect, that should be fixed).
Second, it does need a careful property dance, since the class attribute needs to give a dtype, while the instance attribute should give an error. That said... while I sm not sure that the Python API makes that simple, it alsodoesn't make it hard.

I guess that makesme lean to: worth an issue/followup, but maybe not needed here?

Comment on lines +10 to +12
For array-like objects we encourage you to implement ``__numpy_dtype__``
with a warning or error to _prevent_ using e.g. ``dtype=dataframe`` in
NumPy functions (it may be good to go via a Deprecation).

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.

Do we really need other array libraries to take action? If they don't do anything, the status-quo for these types of array-likes is maintained (works or doesn't work depending on the case).

A warning / deprecation / error can be handled centrally in numpy. You could even make this behavior dependent on whether the passed object is array-like and/or whether they return a numpy dtype.

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.

Let me rephrase it to say that you could do this. NumPy would indeed deprecate it for you as soon as we actually deprecate .dtype.
I agree with that deprecation, but think we may want to wait a little bit, implementing it in the downstream library would be an early opt-in effectively.

@seberg seberg added this to the 2.4.0 release milestone Nov 19, 2025

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

I had a look through the changes to the C code. I can't see any obvious issues in the C code.

dt_instance = dt()
dt_instance.dtype = dt
with pytest.raises(RecursionError):
with pytest.raises(ValueError):

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.

Does this behavior change deserve a release note?

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.

🤷 I don't think recursion errors are a type of error that anyonen should ever run into really.

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

LGTM, just one small comment about whether the changing error type deserves a release note

@charris
charris merged commit e16fc01 into numpy:main Nov 27, 2025
91 of 96 checks passed
@charris

charris commented Nov 27, 2025

Copy link
Copy Markdown
Member

Thanks Sebastian.

@jorenham

Copy link
Copy Markdown
Member

I guess I'll do the typing stuff for this tomorrow then (in 12 hours or so). I assume that branching 2.4 is still planned for his weekend?

@seberg
seberg deleted the __numpy_dtype__ branch November 28, 2025 07:57
@jorenham

jorenham commented Nov 28, 2025

Copy link
Copy Markdown
Member

Maybe we could add a __numpy_dtype__ to dtype itself, i.e.e.g. dtype(int8).__numpy_dtype__ == dtype(int8)?

And how about also adding it to the scalar-type classes, e.g.i.e. int8.__numpy_dtype__ == dtype(int8)?

That way we'd be able to simplify numpy._typing._SupportsDType and numpy.typing.DTypeLike, from

# numpy._typing
class _SupportsDType[DTypeT: np.dtype = np.dtype](Protocol):
    @property
    def dtype(self) -> DTypeT: ...

type _VoidDTypeLike = # <omitted spaghetti>
type _DTypeLike[ScalarT: np.generic] = type[ScalarT] | np.dtype[ScalarT] | _SupportsDType[np.dtype[ScalarT]]

# numpy.typing
type DTypeLike =  np.dtype | type | str | _SupportsDType | _VoidDTypeLike

to just

# numpy._typing
class _SupportsDType[DTypeT: np.dtype = np.dtype](Protocol):
    @property
    def __numpy_dtype__(self) -> DTypeT: ...

type _VoidDTypeLike = # <omitted spaghetti>
type _DTypeLike[ScalarT: np.generic] = _SupportsDType[np.dtype[ScalarT]]

# numpy.typing
type DTypeLike = type | str | _DTypeLike | _VoidDTypeLike

(this uses Python 3.13+ PEP 696 syntax for aesthetics)

Typing aside, I suppose it'd also be nice if we'd "practice what we preach", analogous to e.g. ndarray.__array__, int.__int__, str.__str__, etc.

@seberg

seberg commented Nov 28, 2025

Copy link
Copy Markdown
Member Author

And how about also adding it to the scalar-type classes

Possible, but is it worth it? You need to make a funky dance with a custom descriptor stuffed into the __dict__ again, because you must error when accessing it from the class...

I do also do like to (in principle!) allow tying scalar types to dtypes that do not allow adding a custom attribute like this (becuase they are defined by a 3rd party). I.e. that still needs something like the sctypeDict "registration".

EDIT: Although, I guess typing wise the last thing might not matter too much, in the sense that I am not sure user dtypes are supported typing wise to begin with...

@charris

charris commented Nov 28, 2025

Copy link
Copy Markdown
Member

I assume that branching 2.4 is still planned for his weekend?

Only if we are ready.

@jorenham

Copy link
Copy Markdown
Member

I.e. that still needs something like the sctypeDict "registration".

EDIT: Although, I guess typing wise the last thing might not matter too much, in the sense that I am not sure user dtypes are supported typing wise to begin with...

Yea, that's right. It'd be similar to that ABCMeta.register stuff, that type-checkers also aren't able to deal with. I'm not even sure if there exists a type theory (yes; there's not just one "type-theory") that would allow for such a thing, considering the (arguably) most important static typing axiom, "types don't change" (at runtime), that all type theories share (as far as I'm aware of).

The approach that we currently use to statically determine a scalar type's associated dtype, via its .dtype attribute (with a typing.Protocol). But since it's an instance attribute, and the dtype constructor only accepts scalar classes, that can be pretty awkward to deal with.

@jorenham

jorenham commented Nov 28, 2025

Copy link
Copy Markdown
Member

Possible, but is it worth it? You need to make a funky dance with a custom descriptor stuffed into the __dict__ again, because you must error when accessing it from the class...

Can't we use _DTypeMeta for that?

@seberg

seberg commented Nov 28, 2025

Copy link
Copy Markdown
Member Author

You are confusing scalar types and dtypes.

@jorenham

Copy link
Copy Markdown
Member

cakedev0 pushed a commit to cakedev0/numpy that referenced this pull request Dec 5, 2025
numpy#30179)

* MAINT,API: Introduce __numpy_dtype__ and fix dtype attribute recursion

This is a lot more complex then I expected. We, correctly, deprecated
`.dtype` attribute lookup recursion, however...
The code still had a `try/except` that still recursed and that try
except carried meaning at least in a weird path of:
```
class mydtype(np.void):
    dtype = ...
```

Now does that make sense? Maybe not... we also fall back to object
in some paths which should have been broken when a dtype attribute
existed on a type but it is a property/descriptor.

So, this removes the recursion, but adds a check for `__get__` to
filter out those cases, this is something we successfully did for
other protocols `__array__`, `__array_interface__`, etc.

Signed-off-by: Sebastian Berg <sebastianb@nvidia.com>

* DOC: Add release note snippet

* DOC: Make release note more precise, array-likes don't need the new attr

---------

Signed-off-by: Sebastian Berg <sebastianb@nvidia.com>
IndifferentArea pushed a commit to IndifferentArea/numpy that referenced this pull request Dec 7, 2025
numpy#30179)

* MAINT,API: Introduce __numpy_dtype__ and fix dtype attribute recursion

This is a lot more complex then I expected. We, correctly, deprecated
`.dtype` attribute lookup recursion, however...
The code still had a `try/except` that still recursed and that try
except carried meaning at least in a weird path of:
```
class mydtype(np.void):
    dtype = ...
```

Now does that make sense? Maybe not... we also fall back to object
in some paths which should have been broken when a dtype attribute
existed on a type but it is a property/descriptor.

So, this removes the recursion, but adds a check for `__get__` to
filter out those cases, this is something we successfully did for
other protocols `__array__`, `__array_interface__`, etc.

Signed-off-by: Sebastian Berg <sebastianb@nvidia.com>

* DOC: Add release note snippet

* DOC: Make release note more precise, array-likes don't need the new attr

---------

Signed-off-by: Sebastian Berg <sebastianb@nvidia.com>
eendebakpt added a commit to eendebakpt/numpy that referenced this pull request Sep 8, 2026
numpygh-30179 dropped the assignment of the fetched .dtype attribute, so the
result no longer inherited fields/names/elsize/subarray and a reference
to the attribute leaked on every call. Restore the assignment, copying
the subarray info instead of stealing it from the user-visible
attribute. Un-xfail test_void_subtype and assert the results.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
eendebakpt added a commit to eendebakpt/numpy that referenced this pull request Sep 8, 2026
numpygh-30179 dropped the assignment of the fetched .dtype attribute, so the
result no longer inherited fields/names/elsize/subarray and a reference
to the attribute leaked on every call. Restore the assignment,
inheriting from a void attribute via PyArray_DescrNew, which copies the
subarray info instead of stealing it from the user-visible attribute.
Un-xfail test_void_subtype and assert the results.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

7 participants