Repository: numpy/numpy

Issue:
remove newline in repr of arrays with flexible datatype?
Another one to think about while we are changing print output for 1.14: (see #9699)

Flexible types have an extra newline before the datatype in the repr of the array:


```python
>>> np.array(['hello'])
array(['hello'],
      dtype='<U5')
```

Should we remove the newline?


Discussion:
The newline doesn't seem particularly useful here...
Seems to me that this was targeted at `np.void` structured arrays, but leaked into the formatting of all flexible arrays. I think it's handy for the `np.void` ones, but not for the `np.character` ones
This should be easy to handle with the `legacy` flag now, right?

Resolving patch:
diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py
--- a/numpy/core/arrayprint.py
+++ b/numpy/core/arrayprint.py
@@ -180,7 +180,7 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None,
         position of floats and different behavior for 0d arrays. If set to
         `False`, disables legacy mode. Unrecognized strings will be ignored
         with a warning for forward compatibility.
-        
+
         .. versionadded:: 1.14.0
 
     See Also
@@ -546,7 +546,7 @@ def array2string(a, max_line_width=None, precision=None,
         position of floats and different behavior for 0d arrays. If set to
         `False`, disables legacy mode. Unrecognized strings will be ignored
         with a warning for forward compatibility.
-        
+
         .. versionadded:: 1.14.0
 
     Returns
@@ -1224,6 +1224,9 @@ def array_repr(arr, max_line_width=None, precision=None, suppress_small=None):
     'array([ 0.000001,  0.      ,  2.      ,  3.      ])'
 
     """
+    if max_line_width is None:
+        max_line_width = _format_options['linewidth']
+
     if type(arr) is not ndarray:
         class_name = type(arr).__name__
     else:
@@ -1242,20 +1245,34 @@ def array_repr(arr, max_line_width=None, precision=None, suppress_small=None):
 
     if skipdtype:
         return "%s(%s)" % (class_name, lst)
+
+    # determine typename
+    if issubclass(arr.dtype.type, flexible):
+        if arr.dtype.names:
+            typename = "%s" % str(arr.dtype)
+        else:
+            typename = "'%s'" % str(arr.dtype)
     else:
         typename = arr.dtype.name
-        # Quote typename in the output if it is "complex".
+        # quote typenames which can't be represented as python variable names
         if typename and not (typename[0].isalpha() and typename.isalnum()):
             typename = "'%s'" % typename
 
-        lf = ' '
+    prefix = "{}({},".format(class_name, lst)
+    suffix = "dtype={})".format(typename)
+
+    # compute whether we should put dtype on a new line: Do so if adding the
+    # dtype would extend the last line past max_line_width.
+    # Note: This line gives the correct result even when rfind returns -1.
+    last_line_len = len(prefix) - (prefix.rfind('\n') + 1)
+    spacer = " "
+    if _format_options['legacy'] == '1.13':
         if issubclass(arr.dtype.type, flexible):
-            if arr.dtype.names:
-                typename = "%s" % str(arr.dtype)
-            else:
-                typename = "'%s'" % str(arr.dtype)
-            lf = '\n'+' '*len(class_name + "(")
-        return "%s(%s,%sdtype=%s)" % (class_name, lst, lf, typename)
+            spacer = '\n' + ' '*len(class_name + "(")
+    elif last_line_len + len(suffix) + 1 > max_line_width:
+        spacer = '\n' + ' '*len(class_name + "(")
+
+    return prefix + spacer + suffix
 
 def array_str(a, max_line_width=None, precision=None, suppress_small=None):
     """


Test patch:
diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py
--- a/numpy/core/tests/test_arrayprint.py
+++ b/numpy/core/tests/test_arrayprint.py
@@ -7,6 +7,7 @@
 from numpy.testing import (
      run_module_suite, assert_, assert_equal, assert_raises, assert_warns
 )
+import textwrap
 
 class TestArrayRepr(object):
     def test_nan_inf(self):
@@ -30,8 +31,7 @@ class sub(np.ndarray): pass
         xstruct = np.ones((2,2), dtype=[('a', 'i4')]).view(sub)
         assert_equal(repr(xstruct),
             "sub([[(1,), (1,)],\n"
-            "     [(1,), (1,)]],\n"
-            "    dtype=[('a', '<i4')])"
+            "     [(1,), (1,)]], dtype=[('a', '<i4')])"
         )
 
     def test_self_containing(self):
@@ -199,8 +199,7 @@ def test_unstructured_void_repr(self):
         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']," "\n"
-            r"      dtype='|V8')")
+            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])
@@ -265,10 +264,10 @@ def test_0d_arrays(self):
 
         if sys.version_info[0] >= 3:
             assert_equal(repr(np.array('café', np.unicode_)),
-                         "array('café',\n      dtype='<U4')")
+                         "array('café', dtype='<U4')")
         else:
             assert_equal(repr(np.array(u'café', np.unicode_)),
-                         "array(u'caf\\xe9',\n      dtype='<U4')")
+                         "array(u'caf\\xe9', dtype='<U4')")
         assert_equal(str(np.array('test', np.str_)), 'test')
 
         a = np.zeros(1, dtype=[('a', '<i4', (3,))])
@@ -361,8 +360,8 @@ def test_float_overflow_nowarn(self):
 
     def test_sign_spacing_structured(self):
         a = np.ones(2, dtype='f,f')
-        assert_equal(repr(a), "array([(1., 1.), (1., 1.)],\n"
-                              "      dtype=[('f0', '<f4'), ('f1', '<f4')])")
+        assert_equal(repr(a),
+            "array([(1., 1.), (1., 1.)], dtype=[('f0', '<f4'), ('f1', '<f4')])")
         assert_equal(repr(a[0]), "(1., 1.)")
 
     def test_floatmode(self):
@@ -459,6 +458,21 @@ def test_legacy_mode_scalars(self):
                      '1.1234567891234568')
         assert_equal(str(np.complex128(complex(1, np.nan))), '(1+nanj)')
 
+    def test_dtype_linewidth_wrapping(self):
+        np.set_printoptions(linewidth=75)
+        assert_equal(repr(np.arange(10,20., dtype='f4')),
+            "array([10., 11., 12., 13., 14., 15., 16., 17., 18., 19.], dtype=float32)")
+        assert_equal(repr(np.arange(10,24., dtype='f4')), textwrap.dedent("""\
+            array([10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23.],
+                  dtype=float32)"""))
+
+        styp = '<U4' if sys.version_info[0] >= 3 else '|S4'
+        assert_equal(repr(np.ones(3, dtype=styp)),
+            "array(['1', '1', '1'], dtype='{}')".format(styp))
+        assert_equal(repr(np.ones(12, dtype=styp)), textwrap.dedent("""\
+            array(['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1'],
+                  dtype='{}')""".format(styp)))
+
 def test_unicode_object_array():
     import sys
     if sys.version_info[0] >= 3: