From 2461bc91ba0eb97e66357d6454d1df5836a705cf Mon Sep 17 00:00:00 2001 From: Allan Haldane Date: Thu, 29 Jun 2017 15:20:00 -0400 Subject: [PATCH 1/4] ENH: fix 0d array printing using `str` or `formatter`. The str of 0d arrays now returns `str(a[()])` like scalars, and the repr returns `'array(' + formatter(a[()]) + ')'` like ndarrays. The default implementation of str and repr for user-defined types is removed. Fixes #1415 --- doc/release/1.14.0-notes.rst | 19 +++++++++++++++---- numpy/core/arrayprint.py | 27 ++++++++++++--------------- numpy/core/tests/test_arrayprint.py | 15 +++++++++++++++ 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/doc/release/1.14.0-notes.rst b/doc/release/1.14.0-notes.rst index 100aaee72267..077afdd73983 100644 --- a/doc/release/1.14.0-notes.rst +++ b/doc/release/1.14.0-notes.rst @@ -374,10 +374,21 @@ fewer. Changes ======= -0d arrays now print their elements like other arrays ----------------------------------------------------- -0d arrays now use the array2string formatters to print their elements, like -other arrays. The ``style`` argument of ``array2string`` is now non-functional. +0d array printing changed to be more consistent with scalars/ndarrays +--------------------------------------------------------------------- +Previously the ``str`` and ``repr`` of 0d arrays had idiosyncratic +implementations which returned ``str(a.item())`` and ``'array(' + +repr(a.item()) + ')'`` respectively for 0d array ``a``, unlike both numpy +scalars and higher dimension ndarrays. + +Now, the ``str`` of a 0d array acts like a numpy scalar using ``str(a[()])`` +and the ``repr`` acts like higher dimension arrays using ``formatter(a[()])``, +where ``formatter`` can be specified using ``np.set_printoptions``. + +The ``style`` argument of ``np.array2string`` now accepts the value ``None``, +(the new default), which causes 0d arrays to be printed using the appropriate +``formatter``. Otherwise ``style`` should be a function which accepts a numpy +scalar and returns a string, and ``style(a[()])`` is returned. User-defined types now need to implement ``__str__`` and ``__repr__`` --------------------------------------------------------------------- diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index 2706d16f06a2..f996aa394bdc 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -416,8 +416,8 @@ def _array2string(a, options, separator=' ', prefix=""): def array2string(a, max_line_width=None, precision=None, suppress_small=None, separator=' ', prefix="", - style=np._NoValue, formatter=None, threshold=None, - edgeitems=None, sign=None, floatmode=None): + style=None, formatter=None, threshold=None, + edgeitems=None, sign=None): """ Return a string representation of an array. @@ -441,12 +441,11 @@ def array2string(a, max_line_width=None, precision=None, 'prefix(' + array2string(a) + ')' - The length of the prefix string is used to align the - output correctly. - style : _NoValue, optional - Has no effect, do not use. - - .. deprecated:: 1.14.0 + The length of the prefix string is used to align the output correctly. + style : None or function, optional + Controls the printing of 0d arrays. If `None`, prints the 0d array's + element using the usual array formatting. Otherwise, `style` should be + a function that accepts a numpy scalar and returns a string. formatter : dict of callables, optional If not None, the keys should indicate the type(s) that the respective formatting function applies to. Callables should return a string. @@ -541,18 +540,15 @@ def array2string(a, max_line_width=None, precision=None, '[0x0L 0x1L 0x2L]' """ - # Deprecation 05-16-2017 v1.14 - if style is not np._NoValue: - warnings.warn("'style' argument is deprecated and no longer functional", - DeprecationWarning, stacklevel=3) - overrides = _make_options_dict(precision, threshold, edgeitems, max_line_width, suppress_small, None, None, sign, formatter, floatmode) options = _format_options.copy() options.update(overrides) - if a.size == 0: + if style is not None and a.shape == (): + return style(a[()]) + elif a.size == 0: # treat as a null array if any of shape elements == 0 lst = "[]" else: @@ -1164,7 +1160,8 @@ def array_str(a, max_line_width=None, precision=None, suppress_small=None): '[0 1 2]' """ - return array2string(a, max_line_width, precision, suppress_small, ' ', "") + return array2string(a, max_line_width, precision, suppress_small, + ' ', "", str) def set_string_function(f, repr=True): """ diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py index 1c935dcbc2d3..bc8d6da68496 100644 --- a/numpy/core/tests/test_arrayprint.py +++ b/numpy/core/tests/test_arrayprint.py @@ -241,12 +241,27 @@ def test_formatter_reset(self): assert_equal(repr(x), "array([0., 1., 2.])") def test_0d_arrays(self): + if sys.version_info[0] >= 3: + assert_equal(str(np.array('café', np.unicode_)), 'café') + assert_equal(repr(np.array('café', np.unicode_)), + "array('café',\n dtype=' Date: Tue, 3 Oct 2017 00:03:58 -0400 Subject: [PATCH 2/4] MAINT: create a "legacy" print mode to account for whitespace and 0d changes Fixes #9804 --- doc/release/1.14.0-notes.rst | 98 ++++++++++++++++------------- numpy/core/arrayprint.py | 73 +++++++++++++-------- numpy/core/tests/test_arrayprint.py | 8 ++- 3 files changed, 107 insertions(+), 72 deletions(-) diff --git a/doc/release/1.14.0-notes.rst b/doc/release/1.14.0-notes.rst index 077afdd73983..848ca600763a 100644 --- a/doc/release/1.14.0-notes.rst +++ b/doc/release/1.14.0-notes.rst @@ -182,6 +182,19 @@ raising a ``TypeError``. ``dtype.__getitem__`` raises ``TypeError`` when passed wrong type ----------------------------------------------------------------- When indexed with a float, the dtype object used to raise ``ValueError``. +Unneeded whitespace in float array printing removed, 0d str/repr changes +------------------------------------------------------------------------ +The new default of ``sign='-'`` (see improvements below) means that the +``repr`` of float arrays now often omits the whitespace characters previously +used to display the sign. Also, the printing of 0d arrays has been overhauled, +to print more like other ndarrays or numpy scalars (see improvements), which +subtly changes the whitespace and precision of the reprs. These changes are +likely to break the doctests of downstream users. + +These new behaviors can be disabled to mostly reproduce numpy 1.13 behavior by +enabling the new "legacy" printing mode, by calling +``np.set_printoptions(legacy=True)``, or using the new ``legacy`` argument +to ``np.array2string``. This legacy mode overrides the ``sign`` option. C API changes @@ -256,6 +269,35 @@ Chebyshev points of the first kind. A new ``Chebyshev.interpolate`` class method adds support for interpolation over arbitrary intervals using the scaled and shifted Chebyshev points of the first kind. +``sign`` option added to ``np.setprintoptions`` and ``np.array2string`` +----------------------------------------------------------------------- +This option controls printing of the sign of floating-point types, and may be +one of the characters '-', '+' or ' '. With '+' numpy always prints the sign of +positive values, with ' ' it always prints a space (whitespace character) in +the sign position of positive values, and with '-' it will omit the sign +character for positive values. The new default is '-'. + +Note that this new default changes the float output relative to numpy 1.13. The +old behavior can be obtained by enabling "legacy" printing mode using the +``legacy`` argument to ``np.set_printoptions`` or ``np.array2string``, see +compatibility notes above, + +0d array printing changed to be more consistent with scalars/ndarrays +--------------------------------------------------------------------- +Previously the ``str`` and ``repr`` of 0d arrays had idiosyncratic +implementations which returned ``str(a.item())`` and ``'array(' + +repr(a.item()) + ')'`` respectively for 0d array ``a``, unlike both numpy +scalars and higher dimension ndarrays. + +Now, the ``str`` of a 0d array acts like a numpy scalar using ``str(a[()])`` +and the ``repr`` acts like higher dimension arrays using ``formatter(a[()])``, +where ``formatter`` can be specified using ``np.set_printoptions``. + +The ``style`` argument of ``np.array2string`` now accepts the value ``None``, +(the new default), which causes 0d arrays to be printed using the appropriate +``formatter``. Otherwise ``style`` should be a function which accepts a numpy +scalar and returns a string, and ``style(a[()])`` is returned. + Improvements ============ @@ -374,45 +416,16 @@ fewer. Changes ======= -0d array printing changed to be more consistent with scalars/ndarrays ---------------------------------------------------------------------- -Previously the ``str`` and ``repr`` of 0d arrays had idiosyncratic -implementations which returned ``str(a.item())`` and ``'array(' + -repr(a.item()) + ')'`` respectively for 0d array ``a``, unlike both numpy -scalars and higher dimension ndarrays. - -Now, the ``str`` of a 0d array acts like a numpy scalar using ``str(a[()])`` -and the ``repr`` acts like higher dimension arrays using ``formatter(a[()])``, -where ``formatter`` can be specified using ``np.set_printoptions``. - -The ``style`` argument of ``np.array2string`` now accepts the value ``None``, -(the new default), which causes 0d arrays to be printed using the appropriate -``formatter``. Otherwise ``style`` should be a function which accepts a numpy -scalar and returns a string, and ``style(a[()])`` is returned. - -User-defined types now need to implement ``__str__`` and ``__repr__`` ---------------------------------------------------------------------- -Previously, user-defined types could fall back to a default implementation of -``__str__`` and ``__repr__`` implemented in numpy, but this has now been -removed. Now user-defined types will fall back to the python default -``object.__str__`` and ``object.__repr__``. - ``np.linalg.matrix_rank`` is more efficient for hermitian matrices ------------------------------------------------------------------ The keyword argument ``hermitian`` was added to toggle between standard SVD-based matrix rank calculation and the more efficient eigenvalue-based method for symmetric/hermitian matrices. -Integer and Void scalars are now unaffected by ``np.set_string_function`` -------------------------------------------------------------------------- -Previously the ``str`` and ``repr`` of integer and void scalars could be -controlled by ``np.set_string_function``, unlike most other numpy scalars. This -is no longer the case. - Multiple-field indexing/assignment of structured arrays ------------------------------------------------------- The indexing and assignment of structured arrays with multiple fields has -changed in a number of ways: +changed in a number of ways, as warned about in previous releases. First, indexing a structured array with multiple fields (eg, ``arr[['f1', 'f3']]``) returns a view into the original array instead of a @@ -445,21 +458,18 @@ source to the destination. Using field "titles" in multiple-field indexing is now disallowed, as is repeating a field name in a multiple-field index. -``sign`` option added to ``np.set_printoptions`` and ``np.array2string`` ------------------------------------------------------------------------ -This option controls printing of the sign of floating-point types, and may be -one of the characters '-', '+' or ' ', or the string 'legacy'. With '+' numpy -always prints the sign of positive values, with ' ' it always prints a space -(whitespace character) in the sign position of positive values, and with '-' it -will omit the sign character for positive values, and with 'legacy' it will -behave like ' ' except no space is printed in 0d arrays. The new default is '-'. +User-defined types now need to implement ``__str__`` and ``__repr__`` +--------------------------------------------------------------------- +Previously, user-defined types could fall back to a default implementation of +``__str__`` and ``__repr__`` implemented in numpy, but this has now been +removed. Now user-defined types will fall back to the python default +``object.__str__`` and ``object.__repr__``. -Unneeded whitespace in float array printing removed ---------------------------------------------------- -The new default of ``sign='-'`` (see last note) means that the ``repr`` of -float arrays now often omits the whitespace characters previously used to -display the sign. This new behavior can be disabled to mostly reproduce numpy -1.13 behavior by calling ``np.set_printoptions(sign='legacy')``. +Integer and Void scalars are now unaffected by ``np.set_string_function`` +------------------------------------------------------------------------- +Previously the ``str`` and ``repr`` of integer and void scalars could be +controlled by ``np.set_string_function``, unlike most other numpy scalars. This +is no longer the case. ``threshold`` and ``edgeitems`` options added to ``np.array2string`` ----------------------------------------------------------------- diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index f996aa394bdc..e8cea05f78a2 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -66,11 +66,12 @@ 'nanstr': 'nan', 'infstr': 'inf', 'sign': '-', - 'formatter': None } + 'formatter': None, + 'legacy': False} def _make_options_dict(precision=None, threshold=None, edgeitems=None, linewidth=None, suppress=None, nanstr=None, infstr=None, - sign=None, formatter=None, floatmode=None): + sign=None, formatter=None, floatmode=None, legacy=None): """ make a dictionary out of the non-None arguments, plus sanity checks """ options = {k: v for k, v in locals().items() if v is not None} @@ -78,20 +79,19 @@ def _make_options_dict(precision=None, threshold=None, edgeitems=None, if suppress is not None: options['suppress'] = bool(suppress) - if sign not in [None, '-', '+', ' ', 'legacy']: - raise ValueError("sign option must be one of " - "' ', '+', '-', or 'legacy'") - modes = ['fixed', 'unique', 'maxprec', 'maxprec_equal'] if floatmode not in modes + [None]: raise ValueError("floatmode option must be one of " + ", ".join('"{}"'.format(m) for m in modes)) + if sign not in [None, '-', '+', ' ']: + raise ValueError("sign option must be one of ' ', '+', or '-'") + return options def set_printoptions(precision=None, threshold=None, edgeitems=None, linewidth=None, suppress=None, nanstr=None, infstr=None, - formatter=None, sign=None, floatmode=None): + formatter=None, sign=None, floatmode=None, **kwarg): """ Set printing options. @@ -121,12 +121,11 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None, String representation of floating point not-a-number (default nan). infstr : str, optional String representation of floating point infinity (default inf). - sign : string, either '-', '+', ' ' or 'legacy', optional + sign : string, either '-', '+', or ' ', optional Controls printing of the sign of floating-point types. If '+', always print the sign of positive values. If ' ', always prints a space (whitespace character) in the sign position of positive values. If - '-', omit the sign character of positive values. If 'legacy', print a - space for positive values except in 0d arrays. (default '-') + '-', omit the sign character of positive values. (default '-') formatter : dict of callables, optional If not None, the keys should indicate the type(s) that the respective formatting function applies to. Callables should return a string. @@ -170,6 +169,11 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None, but if every element in the array can be uniquely represented with an equal number of fewer digits, use that many digits for all elements. + legacy : boolean, optional + If True, enables legacy printing mode, which overrides the `sign` + option. Legacy printing mode approximates numpy 1.13 print output, + which includes a space in the sign position of floats and different + behavior for 0d arrays. See Also -------- @@ -219,9 +223,14 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None, ... linewidth=75, nanstr='nan', precision=8, ... suppress=False, threshold=1000, formatter=None) """ + legacy = kwarg.pop('legacy', None) + if kwarg: + msg = "set_printoptions() got unexpected keyword argument '{}'" + raise TypeError(msg.format(kwarg.popitem()[0])) + opt = _make_options_dict(precision, threshold, edgeitems, linewidth, suppress, nanstr, infstr, sign, formatter, - floatmode) + floatmode, legacy) # formatter is always reset opt['formatter'] = formatter _format_options.update(opt) @@ -286,15 +295,16 @@ def repr_format(x): def _get_formatdict(data, **opt): prec, fmode = opt['precision'], opt['floatmode'] supp, sign = opt['suppress'], opt['sign'] + legacy = opt['legacy'] # wrapped in lambdas to avoid taking a code path with the wrong type of data formatdict = { 'bool': lambda: BoolFormat(data), 'int': lambda: IntegerFormat(data), 'float': lambda: - FloatingFormat(data, prec, fmode, supp, sign), + FloatingFormat(data, prec, fmode, supp, sign, legacy=legacy), 'complexfloat': lambda: - ComplexFloatingFormat(data, prec, fmode, supp, sign), + ComplexFloatingFormat(data, prec, fmode, supp, sign, legacy=legacy), 'datetime': lambda: DatetimeFormat(data), 'timedelta': lambda: TimedeltaFormat(data), 'object': lambda: _object_format, @@ -417,7 +427,7 @@ def _array2string(a, options, separator=' ', prefix=""): def array2string(a, max_line_width=None, precision=None, suppress_small=None, separator=' ', prefix="", style=None, formatter=None, threshold=None, - edgeitems=None, sign=None): + edgeitems=None, sign=None, **kwarg): """ Return a string representation of an array. @@ -477,12 +487,11 @@ def array2string(a, max_line_width=None, precision=None, edgeitems : int, optional Number of array items in summary at beginning and end of each dimension. - sign : string, either '-', '+', ' ' or 'legacy', optional + sign : string, either '-', '+', or ' ', optional Controls printing of the sign of floating-point types. If '+', always print the sign of positive values. If ' ', always prints a space (whitespace character) in the sign position of positive values. If - '-', omit the sign character of positive values. If 'legacy', print a - space for positive values except in 0d arrays. + '-', omit the sign character of positive values. floatmode : str, optional Controls the interpretation of the `precision` option for floating-point types. Can take the following values: @@ -500,6 +509,11 @@ def array2string(a, max_line_width=None, precision=None, but if every element in the array can be uniquely represented with an equal number of fewer digits, use that many digits for all elements. + legacy : boolean, optional + If True, enables legacy printing mode, which overrides the `sign` + option. Legacy printing mode approximates numpy 1.13 print output, + which includes a space in the sign position of floats and different + behavior for 0d arrays. Returns ------- @@ -540,12 +554,22 @@ def array2string(a, max_line_width=None, precision=None, '[0x0L 0x1L 0x2L]' """ + legacy = kwarg.pop('legacy', None) + if kwarg: + msg = "array2string() got unexpected keyword argument '{}'" + raise TypeError(msg.format(kwarg.popitem()[0])) + overrides = _make_options_dict(precision, threshold, edgeitems, max_line_width, suppress_small, None, None, - sign, formatter, floatmode) + sign, formatter, floatmode, legacy) options = _format_options.copy() options.update(overrides) + if options['legacy'] and a.shape == () and not a.dtype.names: + if style is None: + style = repr + return style(a.item()) + if style is not None and a.shape == (): return style(a[()]) elif a.size == 0: @@ -634,14 +658,13 @@ def _formatArray(a, format_function, rank, max_line_len, class FloatingFormat(object): """ Formatter for subtypes of np.floating """ - - def __init__(self, data, precision, floatmode, suppress_small, sign=False): + def __init__(self, data, precision, floatmode, suppress_small, sign=False, **kwarg): # for backcompatibility, accept bools if isinstance(sign, bool): sign = '+' if sign else '-' self._legacy = False - if sign == 'legacy': + if kwarg.get('legacy', False): self._legacy = True sign = '-' if data.shape == () else ' ' @@ -939,16 +962,16 @@ def __call__(self, x): class ComplexFloatingFormat(object): """ Formatter for subtypes of np.complexfloating """ - - def __init__(self, x, precision, floatmode, suppress_small, sign=False): + def __init__(self, x, precision, floatmode, suppress_small, + sign=False, **kwarg): # for backcompatibility, accept bools if isinstance(sign, bool): sign = '+' if sign else '-' self.real_format = FloatingFormat(x.real, precision, floatmode, - suppress_small, sign=sign) + suppress_small, sign=sign, **kwarg) self.imag_format = FloatingFormat(x.imag, precision, floatmode, - suppress_small, sign='+') + suppress_small, sign='+', **kwarg) def __call__(self, x): r = self.real_format(x.real) diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py index bc8d6da68496..6f78e9b22bb8 100644 --- a/numpy/core/tests/test_arrayprint.py +++ b/numpy/core/tests/test_arrayprint.py @@ -5,7 +5,7 @@ import numpy as np from numpy.testing import ( - run_module_suite, assert_, assert_equal + run_module_suite, assert_, assert_equal, assert_raises ) class TestArrayRepr(object): @@ -317,11 +317,13 @@ def test_sign_spacing(self): assert_equal(repr(np.array(1.)), 'array(+1.)') assert_equal(repr(b), 'array([+1.234e+09])') - np.set_printoptions(sign='legacy') + np.set_printoptions(legacy=True) assert_equal(repr(a), 'array([ 0., 1., 2., 3.])') - assert_equal(repr(np.array(1.)), 'array(1.)') assert_equal(repr(b), 'array([ 1.23400000e+09])') assert_equal(repr(-b), 'array([ -1.23400000e+09])') + assert_equal(repr(np.array(1.)), 'array(1.0)') + + assert_raises(TypeError, np.set_printoptions, wrongarg=True) def test_sign_spacing_structured(self): a = np.ones(2, dtype='f,f') From 6fb5327de77bed842c7583ab1ad70335044b7d87 Mon Sep 17 00:00:00 2001 From: Allan Haldane Date: Tue, 7 Nov 2017 18:46:34 -0500 Subject: [PATCH 3/4] MAINT: Move 0d special casing to array_str and array_repr --- numpy/core/arrayprint.py | 60 +++++++++++++++++------------ numpy/core/tests/test_arrayprint.py | 9 ++++- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index e8cea05f78a2..dd64be516c3e 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -170,10 +170,9 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None, represented with an equal number of fewer digits, use that many digits for all elements. legacy : boolean, optional - If True, enables legacy printing mode, which overrides the `sign` - option. Legacy printing mode approximates numpy 1.13 print output, - which includes a space in the sign position of floats and different - behavior for 0d arrays. + If True, enables legacy printing mode, which approximates numpy 1.13 + print output by including a space in the sign position of floats and + different behavior for 0d arrays. See Also -------- @@ -426,8 +425,8 @@ def _array2string(a, options, separator=' ', prefix=""): def array2string(a, max_line_width=None, precision=None, suppress_small=None, separator=' ', prefix="", - style=None, formatter=None, threshold=None, - edgeitems=None, sign=None, **kwarg): + style=np._NoValue, formatter=None, threshold=None, + edgeitems=None, sign=None, floatmode=None, **kwarg): """ Return a string representation of an array. @@ -452,10 +451,10 @@ def array2string(a, max_line_width=None, precision=None, 'prefix(' + array2string(a) + ')' The length of the prefix string is used to align the output correctly. - style : None or function, optional - Controls the printing of 0d arrays. If `None`, prints the 0d array's - element using the usual array formatting. Otherwise, `style` should be - a function that accepts a numpy scalar and returns a string. + style : _NoValue, optional + Has no effect, do not use. + + .. deprecated:: 1.14.0 formatter : dict of callables, optional If not None, the keys should indicate the type(s) that the respective formatting function applies to. Callables should return a string. @@ -565,19 +564,20 @@ def array2string(a, max_line_width=None, precision=None, options = _format_options.copy() options.update(overrides) - if options['legacy'] and a.shape == () and not a.dtype.names: - if style is None: - style = repr - return style(a.item()) + if options['legacy']: + if a.shape == () and not a.dtype.names: + return style(a.item()) + elif style is not np._NoValue: + # Deprecation 11-9-2017 v1.14 + warnings.warn("'style' argument is deprecated and no longer functional" + " except in 'legacy' mode", + DeprecationWarning, stacklevel=3) - if style is not None and a.shape == (): - return style(a[()]) - elif a.size == 0: - # treat as a null array if any of shape elements == 0 - lst = "[]" - else: - lst = _array2string(a, options, separator, prefix) - return lst + # treat as a null array if any of shape elements == 0 + if a.size == 0: + return "[]" + + return _array2string(a, options, separator, prefix) def _extendLine(s, line, word, max_line_len, next_line_prefix): @@ -1124,7 +1124,9 @@ def array_repr(arr, max_line_width=None, precision=None, suppress_small=None): else: class_name = "array" - if arr.size > 0 or arr.shape == (0,): + if _format_options['legacy'] and arr.shape == () and not arr.dtype.names: + lst = repr(arr.item()) + elif arr.size > 0 or arr.shape == (0,): lst = array2string(arr, max_line_width, precision, suppress_small, ', ', class_name + "(") else: # show zero-length shape unless it is (0,) @@ -1183,8 +1185,16 @@ def array_str(a, max_line_width=None, precision=None, suppress_small=None): '[0 1 2]' """ - return array2string(a, max_line_width, precision, suppress_small, - ' ', "", str) + if _format_options['legacy'] and a.shape == () and not a.dtype.names: + return str(a.item()) + + # the str of 0d arrays is a special case: It should appear like a scalar, + # so floats are not truncated by `precision`, and strings are not wrapped + # in quotes. So we return the str of the scalar value. + if a.shape == (): + return str(a[()]) + + return array2string(a, max_line_width, precision, suppress_small, ' ', "") def set_string_function(f, repr=True): """ diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py index 6f78e9b22bb8..8123bee6f206 100644 --- a/numpy/core/tests/test_arrayprint.py +++ b/numpy/core/tests/test_arrayprint.py @@ -5,7 +5,7 @@ import numpy as np from numpy.testing import ( - run_module_suite, assert_, assert_equal, assert_raises + run_module_suite, assert_, assert_equal, assert_raises, assert_warns ) class TestArrayRepr(object): @@ -263,6 +263,13 @@ def test_0d_arrays(self): # str is unaffected assert_equal(str(x), "1") + # check `style` arg raises + assert_warns(DeprecationWarning, np.array2string, + np.array(1.), style=repr) + # but not in legacy mode + np.set_printoptions(legacy=True) + np.array2string(np.array(1.), style=repr) + def test_float_spacing(self): x = np.array([1., 2., 3.]) y = np.array([1., 2., -10.]) From bbf543801589270834f270f4bae3e21926a4321a Mon Sep 17 00:00:00 2001 From: Allan Haldane Date: Wed, 8 Nov 2017 19:30:14 -0500 Subject: [PATCH 4/4] DOC: Update release notes about legacy mode and 0d arrays --- doc/release/1.14.0-notes.rst | 59 +++++++++++++++++------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/doc/release/1.14.0-notes.rst b/doc/release/1.14.0-notes.rst index 848ca600763a..3bec4bb8bab1 100644 --- a/doc/release/1.14.0-notes.rst +++ b/doc/release/1.14.0-notes.rst @@ -182,19 +182,19 @@ raising a ``TypeError``. ``dtype.__getitem__`` raises ``TypeError`` when passed wrong type ----------------------------------------------------------------- When indexed with a float, the dtype object used to raise ``ValueError``. -Unneeded whitespace in float array printing removed, 0d str/repr changes ------------------------------------------------------------------------- -The new default of ``sign='-'`` (see improvements below) means that the -``repr`` of float arrays now often omits the whitespace characters previously -used to display the sign. Also, the printing of 0d arrays has been overhauled, -to print more like other ndarrays or numpy scalars (see improvements), which -subtly changes the whitespace and precision of the reprs. These changes are -likely to break the doctests of downstream users. + +Changes to array printing, and the new "legacy" printing mode. +-------------------------------------------------------------- +The new ``sign='-'`` option (see new features below) causes the ``repr`` of +float arrays to often omit a whitespace previously printed in the sign +position, and 0d arrays also now print with small whitespace and precision +differences (see changes below). These changes are likely to break downstream +user's doctests. These new behaviors can be disabled to mostly reproduce numpy 1.13 behavior by -enabling the new "legacy" printing mode, by calling +enabling the new "legacy" printing mode. This is enabled by calling ``np.set_printoptions(legacy=True)``, or using the new ``legacy`` argument -to ``np.array2string``. This legacy mode overrides the ``sign`` option. +to ``np.array2string``. C API changes @@ -277,27 +277,9 @@ positive values, with ' ' it always prints a space (whitespace character) in the sign position of positive values, and with '-' it will omit the sign character for positive values. The new default is '-'. -Note that this new default changes the float output relative to numpy 1.13. The -old behavior can be obtained by enabling "legacy" printing mode using the -``legacy`` argument to ``np.set_printoptions`` or ``np.array2string``, see -compatibility notes above, - -0d array printing changed to be more consistent with scalars/ndarrays ---------------------------------------------------------------------- -Previously the ``str`` and ``repr`` of 0d arrays had idiosyncratic -implementations which returned ``str(a.item())`` and ``'array(' + -repr(a.item()) + ')'`` respectively for 0d array ``a``, unlike both numpy -scalars and higher dimension ndarrays. - -Now, the ``str`` of a 0d array acts like a numpy scalar using ``str(a[()])`` -and the ``repr`` acts like higher dimension arrays using ``formatter(a[()])``, -where ``formatter`` can be specified using ``np.set_printoptions``. - -The ``style`` argument of ``np.array2string`` now accepts the value ``None``, -(the new default), which causes 0d arrays to be printed using the appropriate -``formatter``. Otherwise ``style`` should be a function which accepts a numpy -scalar and returns a string, and ``style(a[()])`` is returned. - +This new default changes the float output relative to numpy 1.13. The old +behavior can be obtained in "legacy" printing mode, see compatibility notes +above. Improvements ============ @@ -471,6 +453,21 @@ Previously the ``str`` and ``repr`` of integer and void scalars could be controlled by ``np.set_string_function``, unlike most other numpy scalars. This is no longer the case. +0d array printing changed, `style` arg of array2string deprecated +----------------------------------------------------------------- +Previously the ``str`` and ``repr`` of 0d arrays had idiosyncratic +implementations which returned ``str(a.item())`` and ``'array(' + +repr(a.item()) + ')'`` respectively for 0d array ``a``, unlike both numpy +scalars and higher dimension ndarrays. + +Now, the ``str`` of a 0d array acts like a numpy scalar using ``str(a[()])`` +and the ``repr`` acts like higher dimension arrays using ``formatter(a[()])``, +where ``formatter`` can be specified using ``np.set_printoptions``. The +``style`` argument of ``np.array2string`` is deprecated. + +This new behavior is disabled in legacy printing mode, see compatibility notes +above. + ``threshold`` and ``edgeitems`` options added to ``np.array2string`` ----------------------------------------------------------------- These options could previously be controlled using ``np.set_printoptions``, but