Repository: pandas-dev/pandas

Issue:
Hashable DataFrames
See this [SO answer](http://stackoverflow.com/a/15411073/1240268), they want to use memoisation.

OP points out this gets different results from (presumably it does it off id)

```
hash(pd.DataFrame([1,2,3])) 
```

Should they be hashable or should hash raise?  (does it defeat the point of hashing if hashing is expensive?)  cc @cpcloud 



Discussion:
i've been thinking about this off and on. a somewhat related issue is that of the empty frame, i.e., `DataFrame()`. i think the ~~PandasObject~~ NDFrame should raise in all cases since that's what `numpy` does (overriding where it makes sense and is useful). i guess u could have the empty DataFrame be hashable but that seems like it's not worth the effort it would take to do, who needs to hash _empty_ DataFrames? 

`series` raises on `__hash__` as should all `NDFrame`, because they are mutable hashing is meaningless. OTOH, `index` are hashable, as they are immutable

`Index`es are currently _not_ hashable, since they try to hash the underlying `ndarray`.

yes..you are right...oh well my argument is bad then!


Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -31,6 +31,11 @@ def save(self, path):
     def load(cls, path):
         return com.load(path)
 
+    def __hash__(self):
+        raise TypeError('{0!r} objects are mutable, thus they cannot be'
+                              ' hashed'.format(self.__class__.__name__))
+
+
     #----------------------------------------------------------------------
     # Axis name business
 
diff --git a/pandas/core/series.py b/pandas/core/series.py
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -527,9 +527,6 @@ def _constructor(self):
     def _can_hold_na(self):
         return not is_integer_dtype(self.dtype)
 
-    def __hash__(self):
-        raise TypeError('unhashable type')
-
     _index = None
     index = lib.SeriesIndex()
 


Test patch:
diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py
--- a/pandas/tests/test_frame.py
+++ b/pandas/tests/test_frame.py
@@ -3109,6 +3109,11 @@ def test_constructor_for_list_with_dtypes(self):
         expected.sort()
         assert_series_equal(result, expected)
 
+    def test_not_hashable(self):
+        df = pd.DataFrame([1])
+        self.assertRaises(TypeError, hash, df)
+        self.assertRaises(TypeError, hash, self.empty)
+
     def test_timedeltas(self):
 
         df = DataFrame(dict(A = Series(date_range('2012-1-1', periods=3, freq='D')),
diff --git a/pandas/tests/test_panel.py b/pandas/tests/test_panel.py
--- a/pandas/tests/test_panel.py
+++ b/pandas/tests/test_panel.py
@@ -46,6 +46,12 @@ def test_cumsum(self):
         cumsum = self.panel.cumsum()
         assert_frame_equal(cumsum['ItemA'], self.panel['ItemA'].cumsum())
 
+    def not_hashable(self):
+        c_empty = Panel()
+        c = Panel(pd.Panel([[[1]]]))
+        self.assertRaises(TypeError, hash, c_empty)
+        self.assertRaises(TypeError, hash, c)
+
 
 class SafeForLongAndSparse(object):
     _multiprocess_can_split_ = True
diff --git a/pandas/tests/test_panel4d.py b/pandas/tests/test_panel4d.py
--- a/pandas/tests/test_panel4d.py
+++ b/pandas/tests/test_panel4d.py
@@ -785,6 +785,11 @@ def test_reindex(self):
             major=self.panel4d.major_axis, copy=False)
         self.assert_(result is self.panel4d)
 
+    def test_not_hashable(self):
+        p4D_empty = Panel4D()
+        self.assertRaises(TypeError, hash, p4D_empty)
+        self.assertRaises(TypeError, hash, self.panel4d)
+
     def test_reindex_like(self):
         # reindex_like
         smaller = self.panel4d.reindex(labels=self.panel4d.labels[:-1],
diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py
--- a/pandas/tests/test_series.py
+++ b/pandas/tests/test_series.py
@@ -579,6 +579,12 @@ def test_setindex(self):
     def test_array_finalize(self):
         pass
 
+    def test_not_hashable(self):
+        s_empty = Series()
+        s = Series([1])
+        self.assertRaises(TypeError, hash, s_empty)
+        self.assertRaises(TypeError, hash, s)
+
     def test_fromValue(self):
 
         nans = Series(np.NaN, index=self.ts.index)