Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ENH: fix 0d array printing using str or formatter. #9332

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 4 commits into from
Nov 12, 2017
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
84 changes: 51 additions & 33 deletions doc/release/1.14.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,19 @@ raising a ``TypeError``.
-----------------------------------------------------------------
When indexed with a float, the dtype object used to raise ``ValueError``.

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. This is enabled by calling
``np.set_printoptions(legacy=True)``, or using the new ``legacy`` argument
to ``np.array2string``.


C API changes
=============
Expand Down Expand Up @@ -256,6 +269,17 @@ 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 '-'.

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
============
Expand Down Expand Up @@ -374,34 +398,16 @@ 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.

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
Expand Down Expand Up @@ -434,21 +440,33 @@ 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.

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``
-----------------------------------------------------------------
Expand Down
104 changes: 67 additions & 37 deletions numpy/core/arrayprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,32 +66,32 @@
'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}

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.

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -170,6 +169,10 @@ 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 approximates numpy 1.13
print output by including a space in the sign position of floats and
different behavior for 0d arrays.

See Also
--------
Expand Down Expand Up @@ -219,9 +222,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)
Expand Down Expand Up @@ -286,15 +294,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,
Expand Down Expand Up @@ -417,7 +426,7 @@ 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):
edgeitems=None, sign=None, floatmode=None, **kwarg):
"""
Return a string representation of an array.

Expand All @@ -441,8 +450,7 @@ 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.
The length of the prefix string is used to align the output correctly.
style : _NoValue, optional
Has no effect, do not use.

Expand Down Expand Up @@ -478,12 +486,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:
Expand All @@ -501,6 +508,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
-------
Expand Down Expand Up @@ -541,23 +553,31 @@ 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)
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']:
if a.shape == () and not a.dtype.names:
return style(a.item())
elif style is not np._NoValue:
# Deprecation 11-9-2017 v1.14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this 2017-11-09 please (like we do elsewhere)? As a European, this format is silly ;)

(yes, I realized that I missed it the first round)

warnings.warn("'style' argument is deprecated and no longer functional"
" except in 'legacy' mode",
DeprecationWarning, stacklevel=3)

# treat as a null array if any of shape elements == 0
if a.size == 0:
# treat as a null array if any of shape elements == 0
lst = "[]"
else:
lst = _array2string(a, options, separator, prefix)
return lst
return "[]"

return _array2string(a, options, separator, prefix)


def _extendLine(s, line, word, max_line_len, next_line_prefix):
Expand Down Expand Up @@ -638,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 ' '

Expand Down Expand Up @@ -943,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)
Expand Down Expand Up @@ -1105,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,)
Expand Down Expand Up @@ -1164,6 +1185,15 @@ def array_str(a, max_line_width=None, precision=None, suppress_small=None):
'[0 1 2]'

"""
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):
Expand Down
Loading