diff --git a/doc/neps/nep-0051-scalar-representation.rst b/doc/neps/nep-0051-scalar-representation.rst index 2761b21f90b2..3f0af0361da8 100644 --- a/doc/neps/nep-0051-scalar-representation.rst +++ b/doc/neps/nep-0051-scalar-representation.rst @@ -232,6 +232,15 @@ found `here `_ Implementation ============== +.. note:: + This part has *not* been implemented in the + `initial PR `_. + A similar change will be required to fix certain cases in printing and + allow fully correct printing e.g. of structured scalars which include + longdoubles. + A similar solution is also expected to be necessary in the future + to allow custom DTypes to correctly print. + The new representations can be mostly implemented on the scalar types with the largest changes needed in the test suite. diff --git a/doc/release/upcoming_changes/22449.change.rst b/doc/release/upcoming_changes/22449.change.rst new file mode 100644 index 000000000000..ba07e64152fa --- /dev/null +++ b/doc/release/upcoming_changes/22449.change.rst @@ -0,0 +1,15 @@ +Representation of NumPy scalars changed +--------------------------------------- +As per :ref:`NEP 51 `, the scalar representation has been +updated to include the type information to avoid confusion with +Python scalars. +The are now printed as ``np.float64(3.0)`` rather than just ``3.0``. +This may disrupt workflows that store representations of numbers +(e.g. to files) making it harder to read them. They should be stored as +explicit strings, for example by using ``str()`` or ``f"{scalar!s}"``. +For the time being, affected users can use ``np.set_printoptions(legacy="1.25")`` +to get the old behavior (with possibly a few exceptions). +Documentation of downstream projects may require larger updates, +if code snippets are tested. We are working on tooling for: +`doctest-plus `__ +to facilitate updates. diff --git a/doc/source/reference/arrays.classes.rst b/doc/source/reference/arrays.classes.rst index 34da836707e6..8b98bddb29a9 100644 --- a/doc/source/reference/arrays.classes.rst +++ b/doc/source/reference/arrays.classes.rst @@ -657,9 +657,9 @@ objects as inputs and returns an iterator that returns tuples providing each of the input sequence elements in the broadcasted result. ->>> for val in np.broadcast([[1,0],[2,3]],[0,1]): +>>> for val in np.broadcast([[1, 0], [2, 3]], [0, 1]): ... print(val) -(1, 0) -(0, 1) -(2, 0) -(3, 1) +(np.int64(1), np.int64(0)) +(np.int64(0), np.int64(1)) +(np.int64(2), np.int64(0)) +(np.int64(3), np.int64(1)) diff --git a/doc/source/reference/arrays.datetime.rst b/doc/source/reference/arrays.datetime.rst index 356ccce337a5..b2add640f96f 100644 --- a/doc/source/reference/arrays.datetime.rst +++ b/doc/source/reference/arrays.datetime.rst @@ -62,32 +62,32 @@ letters, for a "Not A Time" value. A simple ISO date: >>> np.datetime64('2005-02-25') - numpy.datetime64('2005-02-25') + np.datetime64('2005-02-25') From an integer and a date unit, 1 year since the UNIX epoch: >>> np.datetime64(1, 'Y') - numpy.datetime64('1971') + np.datetime64('1971') Using months for the unit: >>> np.datetime64('2005-02') - numpy.datetime64('2005-02') + np.datetime64('2005-02') Specifying just the month, but forcing a 'days' unit: >>> np.datetime64('2005-02', 'D') - numpy.datetime64('2005-02-01') + np.datetime64('2005-02-01') From a date and time: >>> np.datetime64('2005-02-25T03:30') - numpy.datetime64('2005-02-25T03:30') + np.datetime64('2005-02-25T03:30') NAT (not a time): >>> np.datetime64('nat') - numpy.datetime64('NaT') + np.datetime64('NaT') When creating an array of datetimes from a string, it is still possible to automatically select the unit from the inputs, by using the @@ -168,13 +168,13 @@ data type also accepts the string "NAT" in place of the number for a "Not A Time .. admonition:: Example >>> np.timedelta64(1, 'D') - numpy.timedelta64(1,'D') + np.timedelta64(1,'D') >>> np.timedelta64(4, 'h') - numpy.timedelta64(4,'h') + np.timedelta64(4,'h') >>> np.timedelta64('nAt') - numpy.timedelta64('NaT') + np.timedelta64('NaT') Datetimes and Timedeltas work together to provide ways for simple datetime calculations. @@ -182,25 +182,25 @@ simple datetime calculations. .. admonition:: Example >>> np.datetime64('2009-01-01') - np.datetime64('2008-01-01') - numpy.timedelta64(366,'D') + np.timedelta64(366,'D') >>> np.datetime64('2009') + np.timedelta64(20, 'D') - numpy.datetime64('2009-01-21') + np.datetime64('2009-01-21') >>> np.datetime64('2011-06-15T00:00') + np.timedelta64(12, 'h') - numpy.datetime64('2011-06-15T12:00') + np.datetime64('2011-06-15T12:00') >>> np.timedelta64(1,'W') / np.timedelta64(1,'D') 7.0 >>> np.timedelta64(1,'W') % np.timedelta64(10,'D') - numpy.timedelta64(7,'D') + np.timedelta64(7,'D') >>> np.datetime64('nat') - np.datetime64('2009-01-01') - numpy.timedelta64('NaT','D') + np.timedelta64('NaT','D') >>> np.datetime64('2009-01-01') + np.timedelta64('nat') - numpy.datetime64('NaT') + np.datetime64('NaT') There are two Timedelta units ('Y', years and 'M', months) which are treated specially, because how much time they represent changes depending @@ -289,10 +289,10 @@ specified in business days to datetimes with a unit of 'D' (day). .. admonition:: Example >>> np.busday_offset('2011-06-23', 1) - numpy.datetime64('2011-06-24') + np.datetime64('2011-06-24') >>> np.busday_offset('2011-06-23', 2) - numpy.datetime64('2011-06-27') + np.datetime64('2011-06-27') When an input date falls on the weekend or a holiday, :func:`busday_offset` first applies a rule to roll the @@ -308,16 +308,16 @@ The rules most typically used are 'forward' and 'backward'. ValueError: Non-business day date in busday_offset >>> np.busday_offset('2011-06-25', 0, roll='forward') - numpy.datetime64('2011-06-27') + np.datetime64('2011-06-27') >>> np.busday_offset('2011-06-25', 2, roll='forward') - numpy.datetime64('2011-06-29') + np.datetime64('2011-06-29') >>> np.busday_offset('2011-06-25', 0, roll='backward') - numpy.datetime64('2011-06-24') + np.datetime64('2011-06-24') >>> np.busday_offset('2011-06-25', 2, roll='backward') - numpy.datetime64('2011-06-28') + np.datetime64('2011-06-28') In some cases, an appropriate use of the roll and the offset is necessary to get a desired answer. @@ -327,16 +327,16 @@ is necessary to get a desired answer. The first business day on or after a date: >>> np.busday_offset('2011-03-20', 0, roll='forward') - numpy.datetime64('2011-03-21') + np.datetime64('2011-03-21') >>> np.busday_offset('2011-03-22', 0, roll='forward') - numpy.datetime64('2011-03-22') + np.datetime64('2011-03-22') The first business day strictly after a date: >>> np.busday_offset('2011-03-20', 1, roll='backward') - numpy.datetime64('2011-03-21') + np.datetime64('2011-03-21') >>> np.busday_offset('2011-03-22', 1, roll='backward') - numpy.datetime64('2011-03-23') + np.datetime64('2011-03-23') The function is also useful for computing some kinds of days like holidays. In Canada and the U.S., Mother's day is on @@ -346,7 +346,7 @@ weekmask. .. admonition:: Example >>> np.busday_offset('2012-05', 1, roll='forward', weekmask='Sun') - numpy.datetime64('2012-05-13') + np.datetime64('2012-05-13') When performance is important for manipulating many business dates with one particular choice of weekmask and holidays, there is diff --git a/doc/source/reference/maskedarray.generic.rst b/doc/source/reference/maskedarray.generic.rst index 29fc2fe07452..161ce14b76d2 100644 --- a/doc/source/reference/maskedarray.generic.rst +++ b/doc/source/reference/maskedarray.generic.rst @@ -117,7 +117,7 @@ There are several ways to construct a masked array. >>> x.view(ma.MaskedArray) masked_array(data=[(1, 1.0), (2, 2.0)], mask=[(False, False), (False, False)], - fill_value=(999999, 1.e+20), + fill_value=(999999, 1e+20), dtype=[('a', '>> for coord in list_of_coordinates: ... print(coord) - (0, 0) - (0, 1) - (0, 2) - (0, 3) + (np.int64(0), np.int64(0)) + (np.int64(0), np.int64(1)) + (np.int64(0), np.int64(2)) + (np.int64(0), np.int64(3)) + You can also use ``np.nonzero()`` to print the elements in an array that are less than 5 with:: diff --git a/doc/source/user/basics.rec.rst b/doc/source/user/basics.rec.rst index 640cfaa8bfb6..87d4156466a0 100644 --- a/doc/source/user/basics.rec.rst +++ b/doc/source/user/basics.rec.rst @@ -24,7 +24,7 @@ a 32-bit integer named 'age', and 3. a 32-bit float named 'weight'. If you index ``x`` at position 1 you get a structure:: >>> x[1] - ('Fido', 3, 27.) + np.void(('Fido', 3, 27.0), dtype=[('name', '>> x = np.array([(1, 2., 3.)], dtype='i, f, f') >>> scalar = x[0] >>> scalar - (1, 2., 3.) + np.void((1, 2.0, 3.0), dtype=[('f0', '>> type(scalar) diff --git a/numpy/core/_add_newdocs.py b/numpy/core/_add_newdocs.py index 81314df599d4..78209605fd67 100644 --- a/numpy/core/_add_newdocs.py +++ b/numpy/core/_add_newdocs.py @@ -4663,7 +4663,7 @@ >>> x[0] = (9, 10) >>> z[0] - (9, 10) + np.record((9, 10), dtype=[('a', 'i1'), ('b', 'i1')]) Views that change the dtype size (bytes per entry) should normally be avoided on arrays defined by slices, transposes, fortran-ordering, etc.: diff --git a/numpy/core/_add_newdocs_scalars.py b/numpy/core/_add_newdocs_scalars.py index feace363dc0d..b505e9b268c9 100644 --- a/numpy/core/_add_newdocs_scalars.py +++ b/numpy/core/_add_newdocs_scalars.py @@ -267,13 +267,13 @@ def add_newdoc_for_scalar_type(obj, fixed_aliases, doc): Examples -------- >>> np.void(5) - void(b'\x00\x00\x00\x00\x00') + np.void(b'\x00\x00\x00\x00\x00') >>> np.void(b'abcd') - void(b'\x61\x62\x63\x64') - >>> np.void((5, 3.2, "eggs"), dtype="i,d,S5") - (5, 3.2, b'eggs') # looks like a tuple, but is `np.void` + np.void(b'\x61\x62\x63\x64') + >>> np.void((3.2, b'eggs'), dtype="d,S5") + np.void((3.2, b'eggs'), dtype=[('f0', '>> np.void(3, dtype=[('x', np.int8), ('y', np.int8)]) - (3, 3) # looks like a tuple, but is `np.void` + np.void((3, 3), dtype=[('x', 'i1'), ('y', 'i1')]) """) diff --git a/numpy/core/_ufunc_config.py b/numpy/core/_ufunc_config.py index 504532354dca..f931d8d2cc6a 100644 --- a/numpy/core/_ufunc_config.py +++ b/numpy/core/_ufunc_config.py @@ -379,7 +379,7 @@ class errstate: array([nan, inf, inf]) >>> np.sqrt(-1) - nan + np.float64(nan) >>> with np.errstate(invalid='raise'): ... np.sqrt(-1) Traceback (most recent call last): diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index 770452194b83..6da5a5a31f36 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -88,12 +88,14 @@ def _make_options_dict(precision=None, threshold=None, edgeitems=None, options['legacy'] = 113 elif legacy == '1.21': options['legacy'] = 121 + elif legacy == '1.25': + options['legacy'] = 125 elif legacy is None: pass # OK, do nothing. else: warnings.warn( - "legacy printing option can currently only be '1.13', '1.21', or " - "`False`", stacklevel=3) + "legacy printing option can currently only be '1.13', '1.21', " + "'1.25', or `False`", stacklevel=3) if threshold is not None: # forbid the bad threshold arg suggested by stack overflow, gh-12351 @@ -288,6 +290,8 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None, _format_options['sign'] = '-' elif _format_options['legacy'] == 121: set_legacy_print_mode(121) + elif _format_options['legacy'] == 125: + set_legacy_print_mode(125) elif _format_options['legacy'] == sys.maxsize: set_legacy_print_mode(0) @@ -321,7 +325,7 @@ def get_printoptions(): """ opts = _format_options.copy() opts['legacy'] = { - 113: '1.13', 121: '1.21', sys.maxsize: False, + 113: '1.13', 121: '1.21', 125: '1.25', sys.maxsize: False, }[opts['legacy']] return opts @@ -395,9 +399,13 @@ def _object_format(o): return fmt.format(o) def repr_format(x): + if isinstance(x, (np.str_, np.bytes_)): + return repr(x.item()) return repr(x) def str_format(x): + if isinstance(x, (np.str_, np.bytes_)): + return str(x.item()) return str(x) def _get_formatdict(data, *, precision, floatmode, suppress, sign, legacy, @@ -1400,13 +1408,23 @@ def __call__(self, x): return "({})".format(", ".join(str_fields)) -def _void_scalar_repr(x): +def _void_scalar_to_string(x, is_repr=True): """ Implements the repr for structured-void scalars. It is called from the scalartypes.c.src code, and is placed here because it uses the elementwise formatters defined above. """ - return StructuredVoidFormat.from_data(array(x), **_format_options)(x) + options = _format_options.copy() + if options.get('formatter') is None: + options['formatter'] = {} + options['formatter'].setdefault('float_kind', str) + val_repr = StructuredVoidFormat.from_data(array(x), **options)(x) + if not is_repr: + return val_repr + cls = type(x) + cls_fqn = cls.__module__.replace("numpy", "np") + "." + cls.__name__ + void_dtype = np.dtype((np.void, x.dtype)) + return f"{cls_fqn}({val_repr}, dtype={void_dtype!s})" _typelessdata = [int_, float_, complex_, bool_] diff --git a/numpy/core/fromnumeric.py b/numpy/core/fromnumeric.py index e86fcc124113..565c897046e8 100644 --- a/numpy/core/fromnumeric.py +++ b/numpy/core/fromnumeric.py @@ -2786,7 +2786,7 @@ def max(a, axis=None, out=None, keepdims=np._NoValue, initial=np._NoValue, >>> b = np.arange(5, dtype=float) >>> b[2] = np.NaN >>> np.max(b) - nan + np.float64(nan) >>> np.max(b, where=~np.isnan(b), initial=-1) 4.0 >>> np.nanmax(b) @@ -2930,7 +2930,7 @@ def min(a, axis=None, out=None, keepdims=np._NoValue, initial=np._NoValue, >>> b = np.arange(5, dtype=float) >>> b[2] = np.NaN >>> np.min(b) - nan + np.float64(nan) >>> np.min(b, where=~np.isnan(b), initial=10) 0.0 >>> np.nanmin(b) diff --git a/numpy/core/function_base.py b/numpy/core/function_base.py index 00e4e6b0ea84..7f8e6a5e290a 100644 --- a/numpy/core/function_base.py +++ b/numpy/core/function_base.py @@ -505,9 +505,9 @@ def add_newdoc(place, obj, doc, warn_on_python=True): ---------- place : str The absolute name of the module to import from - obj : str + obj : str or None The name of the object to add documentation to, typically a class or - function name + function name. doc : {str, Tuple[str, str], List[Tuple[str, str]]} If a string, the documentation to apply to `obj` diff --git a/numpy/core/src/multiarray/convert.c b/numpy/core/src/multiarray/convert.c index 60c1a1b9b011..d5d06af4f27f 100644 --- a/numpy/core/src/multiarray/convert.c +++ b/numpy/core/src/multiarray/convert.c @@ -246,6 +246,12 @@ PyArray_ToFile(PyArrayObject *self, FILE *fp, char *sep, char *format) PyArray_IterNew((PyObject *)self); n4 = (format ? strlen((const char *)format) : 0); while (it->index < it->size) { + /* + * This is as documented. If we have a low precision float value + * then it may convert to float64 and store unnecessary digits. + * TODO: This could be fixed, by not using `arr.item()` or using + * the array printing/formatting functionality. + */ obj = PyArray_GETITEM(self, it->dataptr); if (obj == NULL) { Py_DECREF(it); @@ -255,7 +261,7 @@ PyArray_ToFile(PyArrayObject *self, FILE *fp, char *sep, char *format) /* * standard writing */ - strobj = PyObject_Repr(obj); + strobj = PyObject_Str(obj); Py_DECREF(obj); if (strobj == NULL) { Py_DECREF(it); diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src index fe753f64b1e8..d98cfa354597 100644 --- a/numpy/core/src/multiarray/scalartypes.c.src +++ b/numpy/core/src/multiarray/scalartypes.c.src @@ -36,6 +36,9 @@ #include "binop_override.h" +/* determines if legacy mode is enabled, global set in multiarraymodule.c */ +extern int npy_legacy_print_mode; + /* * used for allocating a single scalar, so use the default numpy * memory allocators instead of the (maybe) user overrides @@ -279,9 +282,9 @@ static PyObject * genint_type_str(PyObject *self) { PyObject *item, *item_str; - PyArray_Descr *descr = PyArray_DescrFromTypeObject((PyObject *)Py_TYPE(self)); - void *val = scalar_value(self, descr); - switch (descr->type_num) { + PyArray_Descr *dtype = PyArray_DescrFromTypeObject((PyObject *)Py_TYPE(self)); + void *val = scalar_value(self, dtype); + switch (dtype->type_num) { case NPY_BYTE: item = PyLong_FromLong(*(int8_t *)val); break; @@ -319,12 +322,62 @@ genint_type_str(PyObject *self) if (item == NULL) { return NULL; } - item_str = PyObject_Str(item); Py_DECREF(item); return item_str; } +static PyObject * +genint_type_repr(PyObject *self) +{ + PyObject *value_string = genint_type_str(self); + if (value_string == NULL) { + return NULL; + } + if (npy_legacy_print_mode <= 125) { + return value_string; + } + + int num = _typenum_fromtypeobj((PyObject *)Py_TYPE(self), 0); + + PyObject *repr; + if (num == 0) { + /* Not a builtin scalar (presumably), just use the name */ + repr = PyUnicode_FromFormat("%S(%S)", Py_TYPE(self)->tp_name, value_string); + Py_DECREF(value_string); + return repr; + } + + PyArray_Descr *descr = PyArray_DescrFromType(num); /* cannot fail */ + int bitsize = descr->elsize * 8; + Py_DECREF(descr); + if (PyTypeNum_ISUNSIGNED(num)) { + repr = PyUnicode_FromFormat("np.uint%d(%S)", bitsize, value_string); + } + else { + repr = PyUnicode_FromFormat("np.int%d(%S)", bitsize, value_string); + } + Py_DECREF(value_string); + return repr; +} + +static PyObject * +genbool_type_str(PyObject *self) +{ + return PyUnicode_FromString( + ((PyBoolScalarObject *)self)->obval ? "True" : "False"); +} + +static PyObject * +genbool_type_repr(PyObject *self) +{ + if (npy_legacy_print_mode <= 125) { + return genbool_type_str(self); + } + return PyUnicode_FromString( + ((PyBoolScalarObject *)self)->obval ? "np.True_" : "np.False_"); +} + /* * The __format__ method for PEP 3101. */ @@ -421,6 +474,8 @@ format_@name@(@type@ val, npy_bool scientific, /**begin repeat * #form = repr, str# */ +#define IS_@form@ + static PyObject * stringtype_@form@(PyObject *self) { @@ -438,8 +493,17 @@ stringtype_@form@(PyObject *self) } ret = PyBytes_Type.tp_@form@(new); Py_DECREF(new); +#ifdef IS_repr + if (ret == NULL) { + return NULL; + } + if (npy_legacy_print_mode > 125) { + Py_SETREF(ret, PyUnicode_FromFormat("np.bytes_(%S)", ret)); + } +#endif /* IS_repr */ return ret; } +#undef IS_@form@ /**end repeat**/ /* @@ -450,6 +514,8 @@ stringtype_@form@(PyObject *self) /**begin repeat * #form = repr, str# */ +#define IS_@form@ + static PyObject * unicodetype_@form@(PyObject *self) { @@ -473,8 +539,20 @@ unicodetype_@form@(PyObject *self) ret = PyUnicode_Type.tp_@form@(new); Py_DECREF(new); PyMem_Free(ip); + +#ifdef IS_repr + if (ret == NULL) { + return NULL; + } + if (npy_legacy_print_mode > 125) { + Py_SETREF(ret, PyUnicode_FromFormat("np.str_(%S)", ret)); + } +#endif /* IS_repr */ + return ret; } + +#undef IS_@form@ /**end repeat**/ /* @@ -527,14 +605,15 @@ _void_to_hex(const char* argbuf, const Py_ssize_t arglen, } static PyObject * -_void_scalar_repr(PyObject *obj) { - static PyObject *reprfunc = NULL; +_void_scalar_to_string(PyObject *obj, int repr) { + static PyObject *tostring_func = NULL; npy_cache_import("numpy.core.arrayprint", - "_void_scalar_repr", &reprfunc); - if (reprfunc == NULL) { + "_void_scalar_to_string", &tostring_func); + if (tostring_func == NULL) { return NULL; } - return PyObject_CallFunction(reprfunc, "O", obj); + PyObject *is_repr = repr ? Py_True : Py_False; + return PyObject_CallFunctionObjArgs(tostring_func, obj, is_repr, NULL); } static PyObject * @@ -542,9 +621,15 @@ voidtype_repr(PyObject *self) { PyVoidScalarObject *s = (PyVoidScalarObject*) self; if (PyDataType_HASFIELDS(s->descr)) { - return _void_scalar_repr(self); + /* use string on old versions */ + return _void_scalar_to_string(self, npy_legacy_print_mode > 125); + } + if (npy_legacy_print_mode > 125) { + return _void_to_hex(s->obval, s->descr->elsize, "np.void(b'", "\\x", "')"); + } + else { + return _void_to_hex(s->obval, s->descr->elsize, "void(b'", "\\x", "')"); } - return _void_to_hex(s->obval, s->descr->elsize, "void(b'", "\\x", "')"); } static PyObject * @@ -552,7 +637,7 @@ voidtype_str(PyObject *self) { PyVoidScalarObject *s = (PyVoidScalarObject*) self; if (PyDataType_HASFIELDS(s->descr)) { - return _void_scalar_repr(self); + return _void_scalar_to_string(self, 0); } return _void_to_hex(s->obval, s->descr->elsize, "b'", "\\x", "'"); } @@ -591,14 +676,24 @@ datetimetype_repr(PyObject *self) */ if ((scal->obmeta.num == 1 && scal->obmeta.base != NPY_FR_h) || scal->obmeta.base == NPY_FR_GENERIC) { - ret = PyUnicode_FromFormat("numpy.datetime64('%s')", iso); + if (npy_legacy_print_mode > 125) { + ret = PyUnicode_FromFormat("np.datetime64('%s')", iso); + } + else { + ret = PyUnicode_FromFormat("numpy.datetime64('%s')", iso); + } } else { PyObject *meta = metastr_to_unicode(&scal->obmeta, 1); if (meta == NULL) { return NULL; } - ret = PyUnicode_FromFormat("numpy.datetime64('%s','%S')", iso, meta); + if (npy_legacy_print_mode > 125) { + ret = PyUnicode_FromFormat("np.datetime64('%s','%S')", iso, meta); + } + else { + ret = PyUnicode_FromFormat("numpy.datetime64('%s','%S')", iso, meta); + } Py_DECREF(meta); } @@ -637,7 +732,12 @@ timedeltatype_repr(PyObject *self) /* The metadata unit */ if (scal->obmeta.base == NPY_FR_GENERIC) { - ret = PyUnicode_FromFormat("numpy.timedelta64(%S)", val); + if (npy_legacy_print_mode > 125) { + ret = PyUnicode_FromFormat("np.timedelta64(%S)", val); + } + else { + ret = PyUnicode_FromFormat("numpy.timedelta64(%S)", val); + } } else { PyObject *meta = metastr_to_unicode(&scal->obmeta, 1); @@ -645,7 +745,12 @@ timedeltatype_repr(PyObject *self) Py_DECREF(val); return NULL; } - ret = PyUnicode_FromFormat("numpy.timedelta64(%S,'%S')", val, meta); + if (npy_legacy_print_mode > 125) { + ret = PyUnicode_FromFormat("np.timedelta64(%S,'%S')", val, meta); + } + else { + ret = PyUnicode_FromFormat("numpy.timedelta64(%S,'%S')", val, meta); + } Py_DECREF(meta); } Py_DECREF(val); @@ -758,8 +863,6 @@ timedeltatype_str(PyObject *self) * scalars in numpy 1.13. One day we hope to remove it. */ -/* determines if legacy mode is enabled, global set in multiarraymodule.c */ -extern int npy_legacy_print_mode; #define HALFPREC_REPR 5 #define HALFPREC_STR 5 @@ -922,10 +1025,17 @@ legacy_@name@_format@kind@(npy_@name@ val){ * #kind = str, repr# */ +#define IS_@kind@ + /**begin repeat1 * #name = float, double, longdouble# * #Name = Float, Double, LongDouble# * #NAME = FLOAT, DOUBLE, LONGDOUBLE# + * #repr_format = np.float32(%S), np.float64(%S), np.longdouble('%S')# + * #crepr_imag_format = np.complex64(%Sj), np.complex128(%Sj), + * np.clongdouble('%Sj')# + * #crepr_format = np.complex64(%S%Sj), np.complex128(%S%Sj), + * np.clongdouble('%S%Sj')# */ /* helper function choose scientific of fractional output, based on a cutoff */ @@ -956,8 +1066,21 @@ static PyObject * static PyObject * @name@type_@kind@(PyObject *self) { - return @name@type_@kind@_either(PyArrayScalar_VAL(self, @Name@), - TrimMode_LeaveOneZero, TrimMode_DptZeros, 0); + PyObject *string; + string = @name@type_@kind@_either( + PyArrayScalar_VAL(self, @Name@), + TrimMode_LeaveOneZero, TrimMode_DptZeros, 0); + +#ifdef IS_repr + if (string == NULL) { + return NULL; + } + if (npy_legacy_print_mode > 125) { + Py_SETREF(string, PyUnicode_FromFormat("@repr_format@", string)); + } +#endif /* IS_repr */ + + return string; } static PyObject * @@ -976,7 +1099,17 @@ c@name@type_@kind@(PyObject *self) if (istr == NULL) { return NULL; } - PyObject *ret = PyUnicode_FromFormat("%Sj", istr); + PyObject *ret; +#ifdef IS_str + ret = PyUnicode_FromFormat("%Sj", istr); +#else /* IS_repr */ + if (npy_legacy_print_mode <= 125) { + ret = PyUnicode_FromFormat("%Sj", istr); + } + else { + ret = PyUnicode_FromFormat("@crepr_imag_format@", istr); + } +#endif /* IS_repr */ Py_DECREF(istr); return ret; } @@ -1014,10 +1147,21 @@ c@name@type_@kind@(PyObject *self) return NULL; } - PyObject *ret = PyUnicode_FromFormat("(%S%Sj)", rstr, istr); + PyObject *string; +#ifdef IS_str + string = PyUnicode_FromFormat("(%S%Sj)", rstr, istr); +#else /* IS_repr */ + if (npy_legacy_print_mode > 125) { + string = PyUnicode_FromFormat("@crepr_format@", rstr, istr); + } + else { + string = PyUnicode_FromFormat("(%S%Sj)", rstr, istr); + } +#endif /* IS_repr */ + Py_DECREF(rstr); Py_DECREF(istr); - return ret; + return string; } #undef PREC @@ -1038,13 +1182,24 @@ halftype_@kind@(PyObject *self) absval = floatval < 0 ? -floatval : floatval; + PyObject *string; if (absval == 0 || (absval < 1.e16 && absval >= 1.e-4) ) { - return format_half(val, 0, -1, 0, TrimMode_LeaveOneZero, -1, -1, -1); + string = format_half(val, 0, -1, 0, TrimMode_LeaveOneZero, -1, -1, -1); + } + else { + string = format_half(val, 1, -1, 0, TrimMode_DptZeros, -1, -1, -1); } - return format_half(val, 1, -1, 0, TrimMode_DptZeros, -1, -1, -1); +#ifdef IS_str + return string; +#else + if (string == NULL || npy_legacy_print_mode <= 125) { + return string; + } + return PyUnicode_FromFormat("np.float16(%S)", string); +#endif } - +#undef IS_@kind@ /**end repeat**/ /**begin repeat @@ -4065,16 +4220,17 @@ initialize_numeric_types(void) /**begin repeat - * #Type = Bool, Byte, UByte, Short, UShort, Int, UInt, Long, + * #Type = Byte, UByte, Short, UShort, Int, UInt, Long, * ULong, LongLong, ULongLong# */ - /* both str/repr use genint_type_str to avoid trailing "L" of longs */ Py@Type@ArrType_Type.tp_str = genint_type_str; - Py@Type@ArrType_Type.tp_repr = genint_type_str; + Py@Type@ArrType_Type.tp_repr = genint_type_repr; /**end repeat**/ + PyBoolArrType_Type.tp_str = genbool_type_str; + PyBoolArrType_Type.tp_repr = genbool_type_repr; /**begin repeat diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py index 6796b40777fe..72d4e106aee1 100644 --- a/numpy/core/tests/test_arrayprint.py +++ b/numpy/core/tests/test_arrayprint.py @@ -302,7 +302,8 @@ def test_structure_format_mixed(self): def test_structure_format_int(self): # See #8160 - struct_int = np.array([([1, -1],), ([123, 1],)], dtype=[('B', 'i4', 2)]) + struct_int = np.array([([1, -1],), ([123, 1],)], + dtype=[('B', 'i4', 2)]) assert_equal(np.array2string(struct_int), "[([ 1, -1],) ([123, 1],)]") struct_2dint = np.array([([[0, 1], [2, 3]],), ([[12, 0], [0, 0]],)], @@ -319,14 +320,15 @@ def test_structure_format_float(self): def test_unstructured_void_repr(self): a = np.array([27, 91, 50, 75, 7, 65, 10, 8, 27, 91, 51, 49,109, 82,101,100], dtype='u1').view('V8') - assert_equal(repr(a[0]), r"void(b'\x1B\x5B\x32\x4B\x07\x41\x0A\x08')") + assert_equal(repr(a[0]), + r"np.void(b'\x1B\x5B\x32\x4B\x07\x41\x0A\x08')") assert_equal(str(a[0]), r"b'\x1B\x5B\x32\x4B\x07\x41\x0A\x08'") assert_equal(repr(a), r"array([b'\x1B\x5B\x32\x4B\x07\x41\x0A\x08'," "\n" r" b'\x1B\x5B\x33\x31\x6D\x52\x65\x64'], dtype='|V8')") assert_equal(eval(repr(a), vars(np)), a) - assert_equal(eval(repr(a[0]), vars(np)), a[0]) + assert_equal(eval(repr(a[0]), dict(np=np)), a[0]) def test_edgeitems_kwarg(self): # previously the global print options would be taken over the kwarg @@ -505,6 +507,7 @@ def test_any_text(self, text): a = np.array([text, text, text]) # casting a list of them to an array does not e.g. truncate the value assert_equal(a[0], text) + text = text.item() # use raw python strings for repr below # and that np.array2string puts a newline in the expected location expected_repr = "[{0!r} {0!r}\n {0!r}]".format(text) result = np.array2string(a, max_line_width=len(repr(text)) * 2 + 3) @@ -694,7 +697,8 @@ def test_sign_spacing_structured(self): a = np.ones(2, dtype='= repr_precision, reason="repr precision not enough to show eps") -def test_repr_roundtrip(): +def test_str_roundtrip(): # We will only see eps in repr if within printing precision. o = 1 + LD_INFO.eps - assert_equal(np.longdouble(repr(o)), o, "repr was %s" % repr(o)) + assert_equal(np.longdouble(str(o)), o, "str was %s" % str(o)) @pytest.mark.skipif(string_to_longdouble_inaccurate, reason="Need strtold_l") -def test_repr_roundtrip_bytes(): +def test_str_roundtrip_bytes(): o = 1 + LD_INFO.eps - assert_equal(np.longdouble(repr(o).encode("ascii")), o) + assert_equal(np.longdouble(str(o).encode("ascii")), o) @pytest.mark.skipif(string_to_longdouble_inaccurate, reason="Need strtold_l") @@ -59,9 +59,9 @@ def test_array_and_stringlike_roundtrip(strtype): o = 1 + LD_INFO.eps if strtype in (np.bytes_, bytes): - o_str = strtype(repr(o).encode("ascii")) + o_str = strtype(str(o).encode("ascii")) else: - o_str = strtype(repr(o)) + o_str = strtype(str(o)) # Test that `o` is correctly coerced from the string-like assert o == np.longdouble(o_str) @@ -83,7 +83,7 @@ def test_bogus_string(): @pytest.mark.skipif(string_to_longdouble_inaccurate, reason="Need strtold_l") def test_fromstring(): o = 1 + LD_INFO.eps - s = (" " + repr(o))*5 + s = (" " + str(o))*5 a = np.array([o]*5) assert_equal(np.fromstring(s, sep=" ", dtype=np.longdouble), a, err_msg="reading '%s'" % s) @@ -143,7 +143,7 @@ class TestFileBased: ldbl = 1 + LD_INFO.eps tgt = np.array([ldbl]*5) - out = ''.join([repr(t) + '\n' for t in tgt]) + out = ''.join([str(t) + '\n' for t in tgt]) def test_fromfile_bogus(self): with temppath() as path: @@ -275,9 +275,9 @@ def test_tofile_roundtrip(self): # Conversions long double -> string -def test_repr_exact(): +def test_str_exact(): o = 1 + LD_INFO.eps - assert_(repr(o) != '1') + assert_(str(o) != '1') @pytest.mark.skipif(longdouble_longer_than_double, reason="BUG #2376") @@ -314,9 +314,9 @@ def test_array_repr(): class TestCommaDecimalPointLocale(CommaDecimalPointLocale): - def test_repr_roundtrip_foreign(self): + def test_str_roundtrip_foreign(self): o = 1.5 - assert_equal(o, np.longdouble(repr(o))) + assert_equal(o, np.longdouble(str(o))) def test_fromstring_foreign_repr(self): f = 1.234 diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 41758d6ffb4e..a7c89aa4380d 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -1479,7 +1479,7 @@ def test_zero_width_string(self): assert_equal(xx, [[b'', b''], [b'', b'']]) # check for no uninitialized memory due to viewing S0 array assert_equal(xx[:].dtype, xx.dtype) - assert_array_equal(eval(repr(xx), dict(array=np.array)), xx) + assert_array_equal(eval(repr(xx), dict(np=np, array=np.array)), xx) b = io.BytesIO() np.save(b, xx) @@ -5347,14 +5347,13 @@ def test_roundtrip_str(self, x): x = x.real.ravel() s = "@".join(map(str, x)) y = np.fromstring(s, sep="@") - # NB. str imbues less precision nan_mask = ~np.isfinite(x) assert_array_equal(x[nan_mask], y[nan_mask]) - assert_array_almost_equal(x[~nan_mask], y[~nan_mask], decimal=5) + assert_array_equal(x[~nan_mask], y[~nan_mask]) def test_roundtrip_repr(self, x): x = x.real.ravel() - s = "@".join(map(repr, x)) + s = "@".join(map(lambda x: repr(x)[11:-1], x)) y = np.fromstring(s, sep="@") assert_array_equal(x, y) @@ -6067,7 +6066,7 @@ def setup_method(self): np.random.seed(range(3)) self.rmat = np.random.random((4, 5)) self.cmat = self.rmat + 1j * self.rmat - self.omat = np.array([Decimal(repr(r)) for r in self.rmat.flat]) + self.omat = np.array([Decimal(str(r)) for r in self.rmat.flat]) self.omat = self.omat.reshape(4, 5) def test_python_type(self): diff --git a/numpy/core/tests/test_records.py b/numpy/core/tests/test_records.py index b7867a67c6ca..1b6ae7cf4cef 100644 --- a/numpy/core/tests/test_records.py +++ b/numpy/core/tests/test_records.py @@ -132,7 +132,9 @@ def test_0d_recarray_repr(self): dtype=[('f0', '>> np.nansum([1, np.nan, np.NINF]) -inf >>> from numpy.testing import suppress_warnings - >>> with suppress_warnings() as sup: - ... sup.filter(RuntimeWarning) + >>> with np.errstate(invalid="ignore"): ... np.nansum([1, np.nan, np.inf, -np.inf]) # both +/- infinity present - nan + np.float64(nan) """ a, mask = _replace_nan(a, 0) @@ -1191,7 +1190,7 @@ def nanmedian(a, axis=None, out=None, overwrite_input=False, keepdims=np._NoValu array([[10., nan, 4.], [ 3., 2., 1.]]) >>> np.median(a) - nan + np.float64(nan) >>> np.nanmedian(a) 3.0 >>> np.nanmedian(a, axis=0) @@ -1341,7 +1340,7 @@ def nanpercentile( array([[10., nan, 4.], [ 3., 2., 1.]]) >>> np.percentile(a, 50) - nan + np.float64(nan) >>> np.nanpercentile(a, 50) 3.0 >>> np.nanpercentile(a, 50, axis=0) @@ -1504,7 +1503,7 @@ def nanquantile( array([[10., nan, 4.], [ 3., 2., 1.]]) >>> np.quantile(a, 0.5) - nan + np.float64(nan) >>> np.nanquantile(a, 0.5) 3.0 >>> np.nanquantile(a, 0.5, axis=0) diff --git a/numpy/lib/recfunctions.py b/numpy/lib/recfunctions.py index 83ae413c6032..5263092690b7 100644 --- a/numpy/lib/recfunctions.py +++ b/numpy/lib/recfunctions.py @@ -1351,7 +1351,7 @@ def stack_arrays(arrays, defaults=None, usemask=True, asrecarray=False, mask=[(False, False, True), (False, False, True), (False, False, False), (False, False, False), (False, False, False)], - fill_value=(b'N/A', 1.e+20, 1.e+20), + fill_value=(b'N/A', 1e+20, 1e+20), dtype=[('A', 'S3'), ('B', '>> for dt in [np.int32, np.int64, np.float64, np.complex128]: ... np.ma.array([0, 1], dtype=dt).get_fill_value() ... - 999999 - 999999 - 1e+20 - (1e+20+0j) + np.int64(999999) + np.int64(999999) + np.float64(1e+20) + np.complex128(1e+20+0j) >>> x = np.ma.array([0, 1.], fill_value=-np.inf) >>> x.fill_value - -inf + np.float64(-inf) >>> x.fill_value = np.pi >>> x.fill_value - 3.1415926535897931 # may vary + np.float64(3.1415926535897931) Reset to default: >>> x.fill_value = None >>> x.fill_value - 1e+20 + np.float64(1e+20) """ if self._fill_value is None: @@ -4067,7 +4067,22 @@ def __repr__(self): separator=", ", prefix=indents['mask'] + 'mask=', suffix=',') - reprs['fill_value'] = repr(self.fill_value) + + if self._fill_value is None: + self.fill_value # initialize fill_value + + if (self._fill_value.dtype.kind in ("S", "U") + and self.dtype.kind == self._fill_value.dtype.kind): + # Allow strings: "N/A" has length 3 so would mismatch. + fill_repr = repr(self.fill_value.item()) + elif self._fill_value.dtype == self.dtype and not self.dtype == object: + # Guess that it is OK to use the string as item repr. To really + # fix this, it needs new logic (shared with structured scalars) + fill_repr = str(self.fill_value) + else: + fill_repr = repr(self.fill_value) + + reprs['fill_value'] = fill_repr if dtype_needed: reprs['dtype'] = np.core.arrayprint.dtype_short_repr(self.dtype) @@ -6031,7 +6046,7 @@ def ptp(self, axis=None, out=None, fill_value=None, keepdims=False): >>> y.ptp(axis=1) masked_array(data=[ 126, 127, -128, -127], mask=False, - fill_value=999999, + fill_value=np.int64(999999), dtype=int8) A work-around is to use the `view()` method to view the result as @@ -6040,7 +6055,7 @@ def ptp(self, axis=None, out=None, fill_value=None, keepdims=False): >>> y.ptp(axis=1).view(np.uint8) masked_array(data=[126, 127, 128, 129], mask=False, - fill_value=999999, + fill_value=np.int64(999999), dtype=uint8) """ if out is None: @@ -7553,7 +7568,7 @@ def diff(a, /, n=1, axis=-1, prepend=np._NoValue, append=np._NoValue): >>> np.ma.diff(u8_arr) masked_array(data=[255], mask=False, - fill_value=999999, + fill_value=np.int64(999999), dtype=uint8) >>> u8_arr[1,...] - u8_arr[0,...] 255 @@ -7565,7 +7580,7 @@ def diff(a, /, n=1, axis=-1, prepend=np._NoValue, append=np._NoValue): >>> np.ma.diff(i16_arr) masked_array(data=[-1], mask=False, - fill_value=999999, + fill_value=np.int64(999999), dtype=int16) Examples @@ -8406,7 +8421,7 @@ def fromflex(fxarray): [0, 0]], mask=[[False, False], [False, False]], - fill_value=999999, + fill_value=np.int64(999999), dtype=int32) """ diff --git a/numpy/ma/extras.py b/numpy/ma/extras.py index 714be99dc097..23e97a739951 100644 --- a/numpy/ma/extras.py +++ b/numpy/ma/extras.py @@ -191,7 +191,7 @@ def masked_all_like(arr): [--, --, --]], mask=[[ True, True, True], [ True, True, True]], - fill_value=1e+20, + fill_value=np.float64(1e+20), dtype=float32) The dtype of the masked array matches the dtype of `arr`. diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py index 134f9afc05c1..65a41c75ff7c 100644 --- a/numpy/ma/tests/test_core.py +++ b/numpy/ma/tests/test_core.py @@ -556,23 +556,23 @@ def test_str_repr(self): a[1,1] = np.ma.masked assert_equal( repr(a), - textwrap.dedent('''\ + textwrap.dedent(f'''\ masked_array( data=[[1, 2, 3], [4, --, 6]], mask=[[False, False, False], [False, True, False]], - fill_value=999999, + fill_value={np.array(999999)[()]!r}, dtype=int8)''') ) # but not it they're a row vector assert_equal( repr(a[:1]), - textwrap.dedent('''\ + textwrap.dedent(f'''\ masked_array(data=[[1, 2, 3]], mask=[[False, False, False]], - fill_value=999999, + fill_value={np.array(999999)[()]!r}, dtype=int8)''') )