Repository: numpy/numpy

Issue:
np.array_repr fails on np.matrix objects
Cause of #5875:

```python
>>> np.array_repr(np.eye(2).view(np.matrix))
Traceback (most recent call last):
  File "<pyshell#52>", line 1, in <module>
    np.array_repr(np.matrix(np.eye(2)))
  File "C:\Program Files\Python 3.5\lib\site-packages\numpy\core\numeric.py", line 1807, in array_repr
    ', ', "array(")
  File "C:\Program Files\Python 3.5\lib\site-packages\numpy\core\arrayprint.py", line 447, in array2string
    separator, prefix, formatter=formatter)
  File "C:\Program Files\Python 3.5\lib\site-packages\numpy\core\arrayprint.py", line 325, in _array2string
    _summaryEdgeItems, summary_insert)[:-1]
  File "C:\Program Files\Python 3.5\lib\site-packages\numpy\core\arrayprint.py", line 519, in _formatArray
    summary_insert)
  File "C:\Program Files\Python 3.5\lib\site-packages\numpy\core\arrayprint.py", line 496, in _formatArray
    word = format_function(a[-1])
  File "C:\Program Files\Python 3.5\lib\site-packages\numpy\core\arrayprint.py", line 598, in __call__
    if isnan(x):
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
```

Spot the `x[i].shape == x.shape[1:]` fallacy...


Discussion:
At some point we should deprecate matrices and be done with them. After all, the most likely users are newbies and they should be encouraged to use Python 3.5 and later now that there is the `@` operator.
We had consensus way-back-when to at least add a big warning to the docs, though I don't think it ever happened.

At this point we might well be able to add an actual `DeprecationWarning`, though I don't think we can set a schedule for removing them. Maybe we should try.
> At some point we should deprecate matrices and be done with them.

After yet another nasty matrix corner case, I had an angry search of issues in the hope that there was one titled "Finally deprecate np.matrix". I was dissapointed...
Linked issue can probably be closed in favor of this one, anyway
Ugh, @rgommers is right, I forgot about `scipy.sparse`.

Resolving patch:
diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py
--- a/numpy/core/arrayprint.py
+++ b/numpy/core/arrayprint.py
@@ -448,9 +448,9 @@ def _array2string(a, options, separator=' ', prefix=""):
     # skip over array(
     next_line_prefix += " "*len(prefix)
 
-    lst = _formatArray(a, format_function, a.ndim, options['linewidth'],
+    lst = _formatArray(a, format_function, options['linewidth'],
                        next_line_prefix, separator, options['edgeitems'],
-                       summary_insert, options['legacy'])[:-1]
+                       summary_insert, options['legacy'])
     return lst
 
 
@@ -616,14 +616,14 @@ def array2string(a, max_line_width=None, precision=None,
 
 
 def _extendLine(s, line, word, max_line_len, next_line_prefix):
-    if len(line.rstrip()) + len(word.rstrip()) >= max_line_len:
+    if len((line + word).rstrip()) > max_line_len:
         s += line.rstrip() + "\n"
         line = next_line_prefix
     line += word
     return s, line
 
 
-def _formatArray(a, format_function, rank, max_line_len, next_line_prefix,
+def _formatArray(a, format_function, max_line_len, next_line_prefix,
                  separator, edge_items, summary_insert, legacy):
     """formatArray is designed for two modes of operation:
 
@@ -632,71 +632,81 @@ def _formatArray(a, format_function, rank, max_line_len, next_line_prefix,
     2. Summarized output
 
     """
-    if rank == 0:
-        return format_function(a[()]) + '\n'
+    def recurser(index, hanging_indent):
+        """
+        By using this local function, we don't need to recurse with all the
+        arguments. Since this function is not created recursively, the cost is
+        not significant
+        """
+        axis = len(index)
+        axes_left = a.ndim - axis
 
-    show_summary = summary_insert and 2*edge_items < len(a)
+        if axes_left == 0:
+            return format_function(a[index])
 
-    if show_summary:
-        leading_items = edge_items
-        trailing_items = edge_items
-    else:
-        leading_items = 0
-        trailing_items = len(a)
-
-    if rank == 1:
-        s = ""
-        line = next_line_prefix
-        for i in range(leading_items):
-            word = format_function(a[i]) + separator
-            s, line = _extendLine(s, line, word, max_line_len, next_line_prefix)
+        # when recursing, add a space to align with the [ added
+        next_hanging_indent = hanging_indent + ' '
 
+        a_len = a.shape[axis]
+        show_summary = summary_insert and 2*edge_items < a_len
         if show_summary:
-            if legacy == '1.13':
-                word = summary_insert + ", "
-            else:
-                word = summary_insert + separator
-            s, line = _extendLine(s, line, word, max_line_len, next_line_prefix)
+            leading_items = edge_items
+            trailing_items = edge_items
+        else:
+            leading_items = 0
+            trailing_items = a_len
+
+        # stringify the array with the hanging indent on the first line too
+        s = ''
+
+        # last axis (rows) - wrap elements if they would not fit on one line
+        if axes_left == 1:
+            line = hanging_indent
+            for i in range(leading_items):
+                word = recurser(index + (i,), next_hanging_indent) + separator
+                s, line = _extendLine(s, line, word, max_line_len, hanging_indent)
+
+            if show_summary:
+                if legacy == '1.13':
+                    word = summary_insert + ", "
+                else:
+                    word = summary_insert + separator
+                s, line = _extendLine(s, line, word, max_line_len, hanging_indent)
+
+            for i in range(trailing_items, 1, -1):
+                word = recurser(index + (-i,), next_hanging_indent) + separator
+                s, line = _extendLine(s, line, word, max_line_len, hanging_indent)
+
+            word = recurser(index + (-1,), next_hanging_indent)
+            s, line = _extendLine(s, line, word, max_line_len, hanging_indent)
+            s += line
+
+        # other axes - insert newlines between rows
+        else:
+            s = ''
+            line_sep = separator.rstrip() + '\n'*(axes_left - 1)
 
-        for i in range(trailing_items, 1, -1):
-            word = format_function(a[-i]) + separator
-            s, line = _extendLine(s, line, word, max_line_len, next_line_prefix)
+            for i in range(leading_items):
+                s += hanging_indent + recurser(index + (i,), next_hanging_indent) + line_sep
 
-        word = format_function(a[-1])
-        s, line = _extendLine(s, line, word, max_line_len, next_line_prefix)
-        s += line + "]\n"
-        s = '[' + s[len(next_line_prefix):]
-    else:
-        s = '['
-        line_sep = separator.rstrip() + '\n'*(rank - 1)
-        for i in range(leading_items):
-            if i > 0:
-                s += next_line_prefix
-            s += _formatArray(a[i], format_function, rank-1, max_line_len,
-                              " " + next_line_prefix, separator, edge_items,
-                              summary_insert, legacy)
-            s = s.rstrip() + line_sep
+            if show_summary:
+                if legacy == '1.13':
+                    # trailing space, fixed number of newlines, and fixed separator
+                    s += hanging_indent + summary_insert + ", \n"
+                else:
+                    s += hanging_indent + summary_insert + line_sep
 
-        if show_summary:
-            if legacy == '1.13':
-                # trailing space, fixed number of newlines, and fixed separator
-                s += next_line_prefix + summary_insert + ", \n"
-            else:
-                s += next_line_prefix + summary_insert + line_sep
-
-        for i in range(trailing_items, 1, -1):
-            if leading_items or i != trailing_items:
-                s += next_line_prefix
-            s += _formatArray(a[-i], format_function, rank-1, max_line_len,
-                              " " + next_line_prefix, separator, edge_items,
-                              summary_insert, legacy)
-            s = s.rstrip() + line_sep
-        if leading_items or trailing_items > 1:
-            s += next_line_prefix
-        s += _formatArray(a[-1], format_function, rank-1, max_line_len,
-                          " " + next_line_prefix, separator, edge_items,
-                          summary_insert, legacy).rstrip()+']\n'
-    return s
+            for i in range(trailing_items, 1, -1):
+                s += hanging_indent + recurser(index + (-i,), next_hanging_indent) + line_sep
+
+            s += hanging_indent + recurser(index + (-1,), next_hanging_indent)
+
+        # remove the hanging indent, and wrap in []
+        s = '[' + s[len(hanging_indent):] + ']'
+        return s
+
+    # invoke the recursive part with an initial index and prefix
+    return recurser(index=(), hanging_indent=next_line_prefix)
 
 
 class FloatingFormat(object):
diff --git a/numpy/matrixlib/defmatrix.py b/numpy/matrixlib/defmatrix.py
--- a/numpy/matrixlib/defmatrix.py
+++ b/numpy/matrixlib/defmatrix.py
@@ -328,19 +328,6 @@ def __ipow__(self, other):
     def __rpow__(self, other):
         return NotImplemented
 
-    def __repr__(self):
-        s = repr(self.__array__()).replace('array', 'matrix')
-        # now, 'matrix' has 6 letters, and 'array' 5, so the columns don't
-        # line up anymore. We need to add a space.
-        l = s.splitlines()
-        for i in range(1, len(l)):
-            if l[i]:
-                l[i] = ' ' + l[i]
-        return '\n'.join(l)
-
-    def __str__(self):
-        return str(self.__array__())
-
     def _align(self, axis):
         """A convenience function for operations that need to preserve axis
         orientation.


Test patch:
diff --git a/numpy/testing/tests/test_utils.py b/numpy/testing/tests/test_utils.py
--- a/numpy/testing/tests/test_utils.py
+++ b/numpy/testing/tests/test_utils.py
@@ -296,11 +296,13 @@ def test_error_message(self):
 
             (shapes (2,), (1, 2) mismatch)
              x: array([1, 2])
-             y: [repr failed for <matrix>: %d format: a number is required, not matrix]""")
+             y: matrix([[1, 2]])""")
             try:
                 self.assertEqual(msg, msg_reference)
             except AssertionError:
                 self.assertEqual(msg2, msg_reference)
+        else:
+            raise AssertionError("Did not raise")
 
 
 class TestArrayAlmostEqual(_GenericTest, unittest.TestCase):