8000 ENH: Add `__class_getitem__` to `ndarray`, `dtype` and `number` by BvB93 · Pull Request #19879 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

ENH: Add __class_getitem__ to ndarray, dtype and number #19879

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

Merged
merged 10 commits into from
Sep 25, 2021
Merged
Prev Previous commit
Next Next commit
ENH: Add number.__class_getitem__
  • Loading branch information
BvB93 authored and Bas van Beek committed Sep 15, 2021
commit 9ca8076efde53701610352966a39a54602190bb9
2 changes: 2 additions & 0 deletions numpy/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3058,6 +3058,8 @@ class number(generic, Generic[_NBit1]): # type: ignore
def real(self: _ArraySelf) -> _ArraySelf: ...
@property
def imag(self: _ArraySelf) -> _ArraySelf: ...
if sys.version_info >= (3, 9):
def __class_getitem__(self, item: Any) -> GenericAlias: ...
def __int__(self) -> int: ...
def __float__(self) -> float: ...
def __complex__(self) -> complex: ...
Expand Down
31 changes: 31 additions & 0 deletions numpy/core/_add_newdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6500,6 +6500,37 @@ def refer_to_array_attribute(attr, method=True):
add_newdoc('numpy.core.numerictypes', 'generic',
refer_to_array_attribute('view'))

if sys.version_info >= (3, 9):
add_newdoc('numpy.core.numerictypes', 'number', ('__class_getitem__',
"""
__class_getitem__(item, /)

Return a parametrized wrapper around the `~numpy.number` type.

.. versionadded:: 1.22

Returns
-------
alias : types.GenericAlias
A parametrized `~numpy.number` type.

Examples
--------
>>> from typing import Any
>>> import numpy as np

>>> np.signedinteger[Any]
numpy.signedinteger[typing.Any]

Note
----
This method is only available for python 3.9 and later.

See Also
--------
:pep:`585` : Type hinting generics in standard collections.

"""))

##############################################################################
#
Expand Down
65 changes: 65 additions & 0 deletions numpy/core/src/multiarray/scalartypes.c.src
8000
Original file line number Diff line number Diff line change
Expand Up @@ -1805,6 +1805,21 @@ gentype_setflags(PyObject *NPY_UNUSED(self), PyObject *NPY_UNUSED(args),
Py_RETURN_NONE;
}

/*
* Use for concrete np.number subclasses, making them act as if they
* were subtyped from e.g. np.signedinteger[object], thus lacking any
* free subscription parameters. Requires python >= 3.9.
*/
#ifdef Py_GENERICALIASOBJECT_H
static PyObject *
numbertype_class_getitem(PyObject *cls, PyObject *args)
{
return PyErr_Format(PyExc_TypeError,
"There are no type variables left in %s",
((PyTypeObject *)cls)->tp_name);
}
#endif

/*
* casting complex numbers (that don't inherit from Python complex)
* to Python complex
Expand Down Expand Up @@ -2188,13 +2203,29 @@ static PyGetSetDef inttype_getsets[] = {
{NULL, NULL, NULL, NULL, NULL}
};

static PyMethodDef numbertype_methods[] = {
/* for typing; requires python >= 3.9 */
#ifdef Py_GENERICALIASOBJECT_H
{"__class_getitem__",
(PyCFunction)Py_GenericAlias,
METH_CLASS | METH_O, NULL},
#endif
{NULL, NULL, 0, NULL} /* sentinel */
};

/**begin repeat
* #name = cfloat,clongdouble#
*/
static PyMethodDef @name@type_methods[] = {
{"__complex__",
(PyCFunction)@name@_complex,
METH_VARARGS | METH_KEYWORDS, NULL},
/* for typing; requires python >= 3.9 */
#ifdef Py_GENERICALIASOBJECT_H
{"__class_getitem__",
(PyCFunction)numbertype_class_getitem,
METH_CLASS | METH_O, NULL},
#endif
{NULL, NULL, 0, NULL}
};
/**end repeat**/
Expand Down Expand Up @@ -2232,6 +2263,27 @@ static PyMethodDef @name@type_methods[] = {
{"is_integer",
(PyCFunction)@name@_is_integer,
METH_NOARGS, NULL},
/* for typing; requires python >= 3.9 */
#ifdef Py_GENERICALIASOBJECT_H
{"__class_getitem__",
(PyCFunction)numbertype_class_getitem,
METH_CLASS | METH_O, NULL},
#endif
{NULL, NULL, 0, NULL}
};
/**end repeat**/

/**begin repeat
* #name = byte, short, int, long, longlong, ubyte, ushort,
* uint, ulong, ulonglong, timedelta, cdouble#
*/
static PyMethodDef @name@type_methods[] = {
/* for typing; requires python >= 3.9 */
#ifdef Py_GENERICALIASOBJECT_H
{"__class_getitem__",
(PyCFunction)numbertype_class_getitem,
METH_CLASS | METH_O, NULL},
#endif
{NULL, NULL, 0, NULL}
};
/**end repeat**/
Expand Down Expand Up @@ -3951,6 +4003,8 @@ initialize_numeric_types(void)

PyIntegerArrType_Type.tp_getset = inttype_getsets;

PyNumberArrType_Type.tp_methods = numbertype_methods;

/**begin repeat
* #NAME= Number, Integer, SignedInteger, UnsignedInteger, Inexact,
* Floating, ComplexFloating, Flexible, Character#
Expand Down Expand Up @@ -4016,6 +4070,17 @@ initialize_numeric_types(void)

/**end repeat**/

/**begin repeat
* #name = byte, short, int, long, longlong, ubyte, ushort,
* uint, ulong, ulonglong, timedelta, cdouble#
* #Name = Byte, Short, Int, Long, LongLong, UByte, UShort,
* UInt, ULong, ULongLong, Timedelta, CDouble#
*/

Py@Name@ArrType_Type.tp_methods = @name@type_methods;

/**end repeat**/

/* We won't be inheriting from Python Int type. */
PyIntArrType_Type.tp_hash = int_arrtype_hash;

Expand Down
31 changes: 31 additions & 0 deletions numpy/core/tests/test_scalar_methods.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""
Test the scalar constructors, which also do type-coercion
"""
import sys
import fractions
import platform
import types
from typing import Any, Type

import pytest
import numpy as np
Expand Down Expand Up @@ -128,3 +131,31 @@ def test_false(self, code: str) -> None:
if value == 0:
continue
assert not value.is_integer()


@pytest.mark.skipif(sys.version_info < (3, 9), reason="Requires python 3.9")
class TestClassGetItem:
@pytest.mark.parametrize("cls", [
np.number,
np.integer,
np.inexact,
np.unsignedinteger,
np.signedinteger,
np.floating,
np.complexfloating,
])
def test_abc(self, cls: Type[np.number]) -> None:
alias = cls[Any]
assert isinstance(alias, types.GenericAlias)
assert alias.__origin__ is cls

@pytest.mark.parametrize("cls", [np.generic, np.flexible, np.character])
def test_abc_non_numeric(self, cls: Type[np.generic]) -> None:
with pytest.raises(TypeError):
cls[Any]

@pytest.mark.parametrize("code", np.typecodes["All"])
def test_concrete(self, code: str) -> None:
cls = np.dtype(code).type
with pytest.raises(TypeError):
cls[Any]
0