Skip to content

MAINT: Refactor of numpy/core/_type_aliases.py #24679

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 5 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 87 additions & 207 deletions numpy/core/_type_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,217 +17,97 @@

"""

from numpy.core._string_helpers import english_lower
import numpy.core.multiarray as ma
from numpy.core.multiarray import typeinfo, dtype
from numpy.core._dtype import _kind_name

######################################
# Building `sctypeDict` and `allTypes`
######################################

sctypeDict = {} # Contains all leaf-node scalar types with aliases
allTypes = {} # Collect the types we will add to the module
sctypeDict = {}
allTypes = {}
c_names_dict = {}

_abstract_type_names = {
"generic", "integer", "inexact", "floating", "number",
"flexible", "character", "complexfloating", "unsignedinteger",
"signedinteger"
}

# separate the actual type info from the abstract base classes
_abstract_types = {}
_concrete_typeinfo = {}
for k, v in typeinfo.items():
# make all the keys lowercase too
k = english_lower(k)
if isinstance(v, type):
_abstract_types[k] = v
else:
_concrete_typeinfo[k] = v

_concrete_types = {v.type for k, v in _concrete_typeinfo.items()}


def _bits_of(obj):
try:
info = next(v for v in _concrete_typeinfo.values() if v.type is obj)
except StopIteration:
if obj in _abstract_types.values():
msg = "Cannot count the bits of an abstract type"
raise ValueError(msg) from None
for _abstract_type_name in _abstract_type_names:
allTypes[_abstract_type_name] = getattr(ma, _abstract_type_name)

# some third-party type - make a best-guess
return dtype(obj).itemsize * 8
else:
return info.bits


def bitname(obj):
"""Return a bit-width name for a given type object"""
bits = _bits_of(obj)
dt = dtype(obj)
char = dt.kind
base = _kind_name(dt)

if base == 'object':
bits = 0

if bits != 0:
char = "%s%d" % (char, bits // 8)

return base, bits, char


def _add_types():
for name, info in _concrete_typeinfo.items():
# define C-name and insert typenum and typechar references also
allTypes[name] = info.type
sctypeDict[name] = info.type
sctypeDict[info.char] = info.type
sctypeDict[info.num] = info.type

for name, cls in _abstract_types.items():
allTypes[name] = cls
_add_types()

# This is the priority order used to assign the bit-sized NPY_INTxx names, which
# must match the order in npy_common.h in order for NPY_INTxx and np.intxx to be
# consistent.
# If two C types have the same size, then the earliest one in this list is used
# as the sized name.
_int_ctypes = ['long', 'longlong', 'int', 'short', 'byte']
_uint_ctypes = list('u' + t for t in _int_ctypes)

def _add_aliases():
for name, info in _concrete_typeinfo.items():
# these are handled by _add_integer_aliases
if name in _int_ctypes or name in _uint_ctypes:
continue

# insert bit-width version for this class (if relevant)
base, bit, char = bitname(info.type)

myname = "%s%d" % (base, bit)

# ensure that (c)longdouble does not overwrite the aliases assigned to
# (c)double
if name in ('longdouble', 'clongdouble') and myname in allTypes:
continue

if bit != 0 and base != "bool":
# add to the main namespace
allTypes[myname] = info.type
# add mapping for both the bit name
sctypeDict[myname] = info.type

# add forward, reverse, and string mapping to numarray
sctypeDict[char] = info.type

_add_aliases()

def _add_integer_aliases():
seen_bits = set()
for i_ctype, u_ctype in zip(_int_ctypes, _uint_ctypes):
i_info = _concrete_typeinfo[i_ctype]
u_info = _concrete_typeinfo[u_ctype]
bits = i_info.bits # same for both

for info, charname, intname in [
(i_info,'i%d' % (bits//8,), 'int%d' % bits),
(u_info,'u%d' % (bits//8,), 'uint%d' % bits)]:
if bits not in seen_bits:
# sometimes two different types have the same number of bits
# if so, the one iterated over first takes precedence
allTypes[intname] = info.type
sctypeDict[intname] = info.type
sctypeDict[charname] = info.type

seen_bits.add(bits)

_add_integer_aliases()

# We use these later
void = allTypes['void']

#
# Rework the Python names (so that float and complex and int are consistent
# with Python usage)
#
def _set_up_aliases():
type_pairs = [('single', 'float'),
('csingle', 'cfloat'),
('intc', 'int'),
('uintc', 'uint'),
('int_', 'long'),
('uint', 'ulong'),
('bool_', 'bool'),
('bytes_', 'string'),
('str_', 'unicode'),
('object_', 'object'),
('cfloat', 'cdouble')]
for alias, t in type_pairs:
allTypes[alias] = allTypes[t]
sctypeDict[alias] = sctypeDict[t]
# Remove aliases overriding python types and modules
to_remove = ['object', 'int', 'float', 'complex', 'bool',
'string', 'datetime', 'timedelta', 'bytes', 'str']

for t in to_remove:
try:
del allTypes[t]
del sctypeDict[t]
except KeyError:
pass

# Additional aliases in sctypeDict that should not be exposed as attributes
attrs_to_remove = ['ulong', 'long', 'unicode', 'cfloat']

for t in attrs_to_remove:
try:
del allTypes[t]
except KeyError:
pass
_set_up_aliases()


sctypes = {'int': [],
'uint': [],
'float': [],
'complex': [],
'others': [bool, object, bytes, str, void]}


def _add_array_type(typename, bits):
try:
t = allTypes['%s%d' % (typename, bits)]
except KeyError:
pass
else:
sctypes[typename].append(t)

def _set_array_types():
ibytes = [1, 2, 4, 8, 16, 32, 64]
fbytes = [2, 4, 8, 10, 12, 16, 32, 64]
for bytes in ibytes:
bits = 8*bytes
_add_array_type('int', bits)
_add_array_type('uint', bits)
for bytes in fbytes:
bits = 8*bytes
_add_array_type('float', bits)
_add_array_type('complex', 2*bits)
_gi = dtype('p')
if _gi.type not in sctypes['int']:
indx = 0
sz = _gi.itemsize
_lst = sctypes['int']
while (indx < len(_lst) and sz >= _lst[indx](0).itemsize):
indx += 1
sctypes['int'].insert(indx, _gi.type)
sctypes['uint'].insert(indx, dtype('P').type)
_set_array_types()


# Add additional strings to the sctypeDict
_toadd = ['int', ('float', 'double'), ('complex', 'cdouble'),
'bool', 'object', 'str', 'bytes']

for name in _toadd:
if isinstance(name, tuple):
sctypeDict[name[0]] = allTypes[name[1]]
for k, v in typeinfo.items():
if k.startswith("NPY_") and v not in c_names_dict:
c_names_dict[k[4:]] = v
else:
sctypeDict[name] = allTypes['%s_' % name]

del _toadd, name
concrete_type = v.type
allTypes[k] = concrete_type
sctypeDict[k] = concrete_type

_aliases = {
"double": "float64",
"cdouble": "complex128",
"single": "float32",
"csingle": "complex64",
"half": "float16"
}

for k, v in _aliases.items():
sctypeDict[k] = allTypes[v]
allTypes[k] = allTypes[v]

# extra aliases are added only to `sctypeDict`
# to support dtype name access, such as`np.dtype("float")`
_extra_aliases = {
"bool": "bool_",
"float": "float64",
"complex": "complex128",
"object": "object_",
"bytes": "bytes_",
"int": "int_",
"long": "int_",
"ulong": "uint",
"str": "str_",
}

for k, v in _extra_aliases.items():
sctypeDict[k] = allTypes[v]

# include extended precision sized aliases
for is_complex, full_name in [(False, "longdouble"), (True, "clongdouble")]:
longdouble_type: type = allTypes[full_name]

bits: int = dtype(longdouble_type).itemsize * 8
base_name: str = "complex" if is_complex else "float"
extended_prec_name: str = f"{base_name}{bits}"
if extended_prec_name not in allTypes:
sctypeDict[extended_prec_name] = longdouble_type
allTypes[extended_prec_name] = longdouble_type


####################
# Building `sctypes`
####################

sctypes = {"int": [], "uint": [], "float": [], "complex": [], "others": []}

for type_info in set(typeinfo.values()):
if type_info.kind in ["M", "m"]: # exclude timedelta and datetime
continue

concrete_type = type_info.type

# find proper group for each concrete type
for type_group, abstract_type in [
("int", ma.signedinteger), ("uint", ma.unsignedinteger),
("float", ma.floating), ("complex", ma.complexfloating),
("others", ma.generic)
]:
if issubclass(concrete_type, abstract_type):
sctypes[type_group].append(concrete_type)
break

# sort sctype groups by bitsize
for sctype_list in sctypes.values():
sctype_list.sort(key=lambda x: dtype(x).itemsize)
1 change: 0 additions & 1 deletion numpy/core/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,6 @@ src_multiarray = multiarray_gen_headers + [
'src/multiarray/scalarapi.c',
'src/multiarray/strfuncs.c',
'src/multiarray/temp_elide.c',
'src/multiarray/typeinfo.c',
'src/multiarray/usertypes.c',
'src/multiarray/vdot.c',
'src/npysort/quicksort.cpp',
Expand Down
40 changes: 3 additions & 37 deletions numpy/core/numerictypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,7 @@
)

from ._type_aliases import (
sctypeDict,
allTypes,
bitname,
sctypes,
_concrete_types,
_concrete_typeinfo,
_bits_of,
sctypeDict, allTypes, sctypes
)
from ._dtype import _kind_name

Expand Down Expand Up @@ -431,34 +425,6 @@ def issubdtype(arg1, arg2):
return issubclass(arg1, arg2)


# This dictionary allows look up based on any alias for an array data-type
class _typedict(dict):
"""
Base object for a dictionary for look-up with any alias for an array dtype.

Instances of `_typedict` can not be used as dictionaries directly,
first they have to be populated.

"""

def __getitem__(self, obj):
return dict.__getitem__(self, obj2sctype(obj))

_maxvals = _typedict()
_minvals = _typedict()
def _construct_lookups():
for info in _concrete_typeinfo.values():
obj = info.type
if len(info) > 5:
_maxvals[obj] = info.max
_minvals[obj] = info.min
else:
_maxvals[obj] = None
_minvals[obj] = None

_construct_lookups()


@set_module('numpy')
def sctype2char(sctype):
"""
Expand Down Expand Up @@ -506,7 +472,7 @@ def sctype2char(sctype):
sctype = obj2sctype(sctype)
if sctype is None:
raise ValueError("unrecognized type")
if sctype not in _concrete_types:
if sctype not in sctypeDict.values():
# for compatibility
raise KeyError(sctype)
return dtype(sctype).char
Expand All @@ -519,7 +485,7 @@ def _scalar_type_key(typ):


ScalarType = [int, float, complex, bool, bytes, str, memoryview]
ScalarType += sorted(_concrete_types, key=_scalar_type_key)
ScalarType += sorted(set(sctypeDict.values()), key=_scalar_type_key)
ScalarType = tuple(ScalarType)


Expand Down
4 changes: 0 additions & 4 deletions numpy/core/numerictypes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,6 @@ class _TypeCodes(TypedDict):
Datetime: L['Mm']
All: L['?bhilqpBHILQPefdgFDGSUVOMm']

class _typedict(dict[type[generic], _T]):
def __getitem__(self, key: DTypeLike) -> _T: ...

if sys.version_info >= (3, 10):
_TypeTuple = (
type[Any]
Expand Down Expand Up @@ -116,7 +113,6 @@ def issubdtype(arg1: DTypeLike, arg2: DTypeLike) -> bool: ...

def sctype2char(sctype: DTypeLike) -> str: ...

cast: _typedict[_CastFunc]
typecodes: _TypeCodes
ScalarType: tuple[
type[int],
Expand Down
Loading