Repository: pandas-dev/pandas

Issue:
FrozenNDArray cannot repr scalars
on master:

```
>>> from pandas.core.base import FrozenNDArray
>>> a = FrozenNDArray(1)
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.4/site-packages/pandas-0.15.1_192_g7bd1b24-py3.4-linux-x86_64.egg/pandas/core/base.py", line 65, in __repr__
    return str(self)
  File "/usr/lib/python3.4/site-packages/pandas-0.15.1_192_g7bd1b24-py3.4-linux-x86_64.egg/pandas/core/base.py", line 44, in __str__
    return self.__unicode__()
  File "/usr/lib/python3.4/site-packages/pandas-0.15.1_192_g7bd1b24-py3.4-linux-x86_64.egg/pandas/core/base.py", line 268, in __unicode__
...
...
RuntimeError: maximum recursion depth exceeded while calling a Python object
```

keeps going back and forth between `__str__` and `__unicode__`. a workaround would be to change `self` on [this line](https://github.com/pydata/pandas/blob/e51eb9eca56f31055309d83c592cbefa4a14e42c/pandas/core/base.py#L267) to `self if self.ndim else self.item()`.

I ran into this bug while taking `.max` on the array:

```
>>> xs
FrozenNDArray([1, 2, 3], dtype='int64')
>>> xs.max()
RuntimeError: maximum recursion depth exceeded while calling a Python object
```

it is kind of unusual to me that here `.max` returns a zero dimensional array, whereas numpy returns a scalar:

```
>>> xs
FrozenNDArray([1, 2, 3], dtype='int64')
>>> type(xs.max())
<class 'pandas.core.base.FrozenNDArray'>
>>> type(np.array([1, 2, 3]).max())
<class 'numpy.int64'>
```

in particular, `np.isscalar` returns differently for the two:

```
>>> np.isscalar(np.array([1, 2, 3]).max())
True
>>> np.isscalar(FrozenNDArray([1, 2, 3]).max())
False
```



Discussion:
this whole notion of a FrozenNDarray can be taken out actually
it's only used in 1 place and u can just substitute a ndarray there

this was originally was needed to make index immutable but that's handled elsewhere now 

Can we yank FrozenNDArray now, then?  I guess we'd need to make sure that pickles would still work, but apart from that, as you note, all the references seem very shallow now.
@dsm054 yes as I said a while back its not necessary. pickle compat is 'easy', just follow the pattern in ``pandas.compat.pickle_compat.py`` (might need a slight modification as this is not a rename but shouldn't be too hard).
xref https://github.com/pandas-dev/pandas/pull/15477 and this https://github.com/pandas-dev/pandas/pull/15506 (last was reverted, something odd changed in making the doc build take way longer).

Resolving patch:
diff --git a/pandas/compat/pickle_compat.py b/pandas/compat/pickle_compat.py
--- a/pandas/compat/pickle_compat.py
+++ b/pandas/compat/pickle_compat.py
@@ -60,6 +60,17 @@ def load_reduce(self):
         ('pandas.core.arrays', 'SparseArray'),
 
     # 15477
+    #
+    # TODO: When FrozenNDArray is removed, add
+    # the following lines for compat:
+    #
+    # ('pandas.core.base', 'FrozenNDArray'):
+    #     ('numpy', 'ndarray'),
+    # ('pandas.core.indexes.frozen', 'FrozenNDArray'):
+    #     ('numpy', 'ndarray'),
+    #
+    # Afterwards, remove the current entry
+    # for `pandas.core.base.FrozenNDArray`.
     ('pandas.core.base', 'FrozenNDArray'):
         ('pandas.core.indexes.frozen', 'FrozenNDArray'),
     ('pandas.core.base', 'FrozenList'):
diff --git a/pandas/core/indexes/frozen.py b/pandas/core/indexes/frozen.py
--- a/pandas/core/indexes/frozen.py
+++ b/pandas/core/indexes/frozen.py
@@ -8,6 +8,7 @@
 
 """
 
+import warnings
 import numpy as np
 from pandas.core.base import PandasObject
 from pandas.util._decorators import deprecate_kwarg
@@ -86,6 +87,10 @@ class FrozenNDArray(PandasObject, np.ndarray):
 
     # no __array_finalize__ for now because no metadata
     def __new__(cls, data, dtype=None, copy=False):
+        warnings.warn("\nFrozenNDArray is deprecated and will be removed in a "
+                      "future version.\nPlease use `numpy.ndarray` instead.\n",
+                      FutureWarning, stacklevel=2)
+
         if copy is None:
             copy = not isinstance(data, FrozenNDArray)
         res = np.array(data, dtype=dtype, copy=copy).view(cls)
diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py
--- a/pandas/core/indexes/multi.py
+++ b/pandas/core/indexes/multi.py
@@ -40,8 +40,7 @@
     Index, ensure_index,
     InvalidIndexError,
     _index_shared_docs)
-from pandas.core.indexes.frozen import (
-    FrozenNDArray, FrozenList, _ensure_frozen)
+from pandas.core.indexes.frozen import FrozenList, _ensure_frozen
 import pandas.core.indexes.base as ibase
 _index_doc_kwargs = dict(ibase._index_doc_kwargs)
 _index_doc_kwargs.update(
@@ -1655,7 +1654,7 @@ def _assert_take_fillable(self, values, indices, allow_fill=True,
                 for new_label in taken:
                     label_values = new_label.values()
                     label_values[mask] = na_value
-                    masked.append(FrozenNDArray(label_values))
+                    masked.append(np.asarray(label_values))
                 taken = masked
         else:
             taken = [lab.take(indices) for lab in self.labels]


Test patch:
diff --git a/pandas/tests/indexes/test_frozen.py b/pandas/tests/indexes/test_frozen.py
--- a/pandas/tests/indexes/test_frozen.py
+++ b/pandas/tests/indexes/test_frozen.py
@@ -1,3 +1,4 @@
+import warnings
 import numpy as np
 
 from pandas.compat import u
@@ -34,13 +35,23 @@ def test_inplace(self):
 
 class TestFrozenNDArray(CheckImmutable, CheckStringMixin):
     mutable_methods = ('put', 'itemset', 'fill')
-    unicode_container = FrozenNDArray([u("\u05d0"), u("\u05d1"), "c"])
 
-    def setup_method(self, method):
+    def setup_method(self, _):
         self.lst = [3, 5, 7, -2]
-        self.container = FrozenNDArray(self.lst)
         self.klass = FrozenNDArray
 
+        with warnings.catch_warnings(record=True):
+            warnings.simplefilter("ignore", FutureWarning)
+
+            self.container = FrozenNDArray(self.lst)
+            self.unicode_container = FrozenNDArray(
+                [u("\u05d0"), u("\u05d1"), "c"])
+
+    def test_constructor_warns(self):
+        # see gh-9031
+        with tm.assert_produces_warning(FutureWarning):
+            FrozenNDArray([1, 2, 3])
+
     def test_shallow_copying(self):
         original = self.container.copy()
         assert isinstance(self.container.view(), FrozenNDArray)