Skip to content

Commit 77fae3a

Browse files
committed
CLN: split off frozen (immutable) data structures into pandas/indexes/frozen.py
1 parent f4edb05 commit 77fae3a

File tree

6 files changed

+201
-185
lines changed

6 files changed

+201
-185
lines changed

pandas/core/base.py

-105
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from pandas.util.decorators import (Appender, cache_readonly,
1818
deprecate_kwarg, Substitution)
1919
from pandas.core.common import AbstractMethodError
20-
from pandas.formats.printing import pprint_thing
2120

2221
_shared_docs = dict()
2322
_indexops_doc_kwargs = dict(klass='IndexOpsMixin', inplace='',
@@ -694,110 +693,6 @@ def _gotitem(self, key, ndim, subset=None):
694693
return self
695694

696695

697-
class FrozenList(PandasObject, list):
698-
699-
"""
700-
Container that doesn't allow setting item *but*
701-
because it's technically non-hashable, will be used
702-
for lookups, appropriately, etc.
703-
"""
704-
# Sidenote: This has to be of type list, otherwise it messes up PyTables
705-
# typechecks
706-
707-
def __add__(self, other):
708-
if isinstance(other, tuple):
709-
other = list(other)
710-
return self.__class__(super(FrozenList, self).__add__(other))
711-
712-
__iadd__ = __add__
713-
714-
# Python 2 compat
715-
def __getslice__(self, i, j):
716-
return self.__class__(super(FrozenList, self).__getslice__(i, j))
717-
718-
def __getitem__(self, n):
719-
# Python 3 compat
720-
if isinstance(n, slice):
721-
return self.__class__(super(FrozenList, self).__getitem__(n))
722-
return super(FrozenList, self).__getitem__(n)
723-
724-
def __radd__(self, other):
725-
if isinstance(other, tuple):
726-
other = list(other)
727-
return self.__class__(other + list(self))
728-
729-
def __eq__(self, other):
730-
if isinstance(other, (tuple, FrozenList)):
731-
other = list(other)
732-
return super(FrozenList, self).__eq__(other)
733-
734-
__req__ = __eq__
735-
736-
def __mul__(self, other):
737-
return self.__class__(super(FrozenList, self).__mul__(other))
738-
739-
__imul__ = __mul__
740-
741-
def __reduce__(self):
742-
return self.__class__, (list(self),)
743-
744-
def __hash__(self):
745-
return hash(tuple(self))
746-
747-
def _disabled(self, *args, **kwargs):
748-
"""This method will not function because object is immutable."""
749-
raise TypeError("'%s' does not support mutable operations." %
750-
self.__class__.__name__)
751-
752-
def __unicode__(self):
753-
return pprint_thing(self, quote_strings=True,
754-
escape_chars=('\t', '\r', '\n'))
755-
756-
def __repr__(self):
757-
return "%s(%s)" % (self.__class__.__name__,
758-
str(self))
759-
760-
__setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled
761-
pop = append = extend = remove = sort = insert = _disabled
762-
763-
764-
class FrozenNDArray(PandasObject, np.ndarray):
765-
766-
# no __array_finalize__ for now because no metadata
767-
def __new__(cls, data, dtype=None, copy=False):
768-
if copy is None:
769-
copy = not isinstance(data, FrozenNDArray)
770-
res = np.array(data, dtype=dtype, copy=copy).view(cls)
771-
return res
772-
773-
def _disabled(self, *args, **kwargs):
774-
"""This method will not function because object is immutable."""
775-
raise TypeError("'%s' does not support mutable operations." %
776-
self.__class__)
777-
778-
__setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled
779-
put = itemset = fill = _disabled
780-
781-
def _shallow_copy(self):
782-
return self.view()
783-
784-
def values(self):
785-
"""returns *copy* of underlying array"""
786-
arr = self.view(np.ndarray).copy()
787-
return arr
788-
789-
def __unicode__(self):
790-
"""
791-
Return a string representation for this object.
792-
793-
Invoked by unicode(df) in py2 only. Yields a Unicode String in both
794-
py2/py3.
795-
"""
796-
prepr = pprint_thing(self, escape_chars=('\t', '\r', '\n'),
797-
quote_strings=True)
798-
return "%s(%s, dtype='%s')" % (type(self).__name__, prepr, self.dtype)
799-
800-
801696
class IndexOpsMixin(object):
802697
""" common ops mixin to support a unified inteface / docs for Series /
803698
Index

pandas/indexes/base.py

+2-11
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,15 @@
3535
needs_i8_conversion,
3636
is_iterator, is_list_like,
3737
is_scalar)
38-
from pandas.types.cast import _coerce_indexer_dtype
3938
from pandas.core.common import (is_bool_indexer,
4039
_values_from_object,
4140
_asarray_tuplesafe)
4241

43-
from pandas.core.base import (PandasObject, FrozenList, FrozenNDArray,
44-
IndexOpsMixin)
42+
from pandas.core.base import PandasObject, IndexOpsMixin
4543
import pandas.core.base as base
4644
from pandas.util.decorators import (Appender, Substitution, cache_readonly,
4745
deprecate, deprecate_kwarg)
46+
from pandas.indexes.frozen import FrozenList
4847
import pandas.core.common as com
4948
import pandas.types.concat as _concat
5049
import pandas.core.missing as missing
@@ -3844,14 +3843,6 @@ def _get_na_value(dtype):
38443843
np.timedelta64: tslib.NaT}.get(dtype, np.nan)
38453844

38463845

3847-
def _ensure_frozen(array_like, categories, copy=False):
3848-
array_like = _coerce_indexer_dtype(array_like, categories)
3849-
array_like = array_like.view(FrozenNDArray)
3850-
if copy:
3851-
array_like = array_like.copy()
3852-
return array_like
3853-
3854-
38553846
def _ensure_has_len(seq):
38563847
"""If seq is an iterator, put its values into a list."""
38573848
try:

pandas/indexes/frozen.py

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""
2+
frozen (immutable) data structures to support MultiIndexing
3+
4+
These are used for:
5+
6+
- .names (FrozenList)
7+
- .levels & .labels (FrozenNDArray)
8+
9+
"""
10+
11+
import numpy as np
12+
from pandas.core.base import PandasObject
13+
from pandas.types.cast import _coerce_indexer_dtype
14+
from pandas.formats.printing import pprint_thing
15+
16+
17+
class FrozenList(PandasObject, list):
18+
19+
"""
20+
Container that doesn't allow setting item *but*
21+
because it's technically non-hashable, will be used
22+
for lookups, appropriately, etc.
23+
"""
24+
# Sidenote: This has to be of type list, otherwise it messes up PyTables
25+
# typechecks
26+
27+
def __add__(self, other):
28+
if isinstance(other, tuple):
29+
other = list(other)
30+
return self.__class__(super(FrozenList, self).__add__(other))
31+
32+
__iadd__ = __add__
33+
34+
# Python 2 compat
35+
def __getslice__(self, i, j):
36+
return self.__class__(super(FrozenList, self).__getslice__(i, j))
37+
38+
def __getitem__(self, n):
39+
# Python 3 compat
40+
if isinstance(n, slice):
41+
return self.__class__(super(FrozenList, self).__getitem__(n))
42+
return super(FrozenList, self).__getitem__(n)
43+
44+
def __radd__(self, other):
45+
if isinstance(other, tuple):
46+
other = list(other)
47+
return self.__class__(other + list(self))
48+
49+
def __eq__(self, other):
50+
if isinstance(other, (tuple, FrozenList)):
51+
other = list(other)
52+
return super(FrozenList, self).__eq__(other)
53+
54+
__req__ = __eq__
55+
56+
def __mul__(self, other):
57+
return self.__class__(super(FrozenList, self).__mul__(other))
58+
59+
__imul__ = __mul__
60+
61+
def __reduce__(self):
62+
return self.__class__, (list(self),)
63+
64+
def __hash__(self):
65+
return hash(tuple(self))
66+
67+
def _disabled(self, *args, **kwargs):
68+
"""This method will not function because object is immutable."""
69+
raise TypeError("'%s' does not support mutable operations." %
70+
self.__class__.__name__)
71+
72+
def __unicode__(self):
73+
return pprint_thing(self, quote_strings=True,
74+
escape_chars=('\t', '\r', '\n'))
75+
76+
def __repr__(self):
77+
return "%s(%s)" % (self.__class__.__name__,
78+
str(self))
79+
80+
__setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled
81+
pop = append = extend = remove = sort = insert = _disabled
82+
83+
84+
class FrozenNDArray(PandasObject, np.ndarray):
85+
86+
# no __array_finalize__ for now because no metadata
87+
def __new__(cls, data, dtype=None, copy=False):
88+
if copy is None:
89+
copy = not isinstance(data, FrozenNDArray)
90+
res = np.array(data, dtype=dtype, copy=copy).view(cls)
91+
return res
92+
93+
def _disabled(self, *args, **kwargs):
94+
"""This method will not function because object is immutable."""
95+
raise TypeError("'%s' does not support mutable operations." %
96+
self.__class__)
97+
98+
__setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled
99+
put = itemset = fill = _disabled
100+
101+
def _shallow_copy(self):
102+
return self.view()
103+
104+
def values(self):
105+
"""returns *copy* of underlying array"""
106+
arr = self.view(np.ndarray).copy()
107+
return arr
108+
109+
def __unicode__(self):
110+
"""
111+
Return a string representation for this object.
112+
113+
Invoked by unicode(df) in py2 only. Yields a Unicode String in both
114+
py2/py3.
115+
"""
116+
prepr = pprint_thing(self, escape_chars=('\t', '\r', '\n'),
117+
quote_strings=True)
118+
return "%s(%s, dtype='%s')" % (type(self).__name__, prepr, self.dtype)
119+
120+
121+
def _ensure_frozen(array_like, categories, copy=False):
122+
array_like = _coerce_indexer_dtype(array_like, categories)
123+
array_like = array_like.view(FrozenNDArray)
124+
if copy:
125+
array_like = array_like.copy()
126+
return array_like

pandas/indexes/multi.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
UnsortedIndexError)
2929

3030

31-
from pandas.core.base import FrozenList
3231
import pandas.core.base as base
3332
from pandas.util.decorators import (Appender, cache_readonly,
3433
deprecate, deprecate_kwarg)
@@ -39,9 +38,10 @@
3938

4039
from pandas.core.config import get_option
4140

42-
from pandas.indexes.base import (Index, _ensure_index, _ensure_frozen,
41+
from pandas.indexes.base import (Index, _ensure_index,
4342
_get_na_value, InvalidIndexError,
4443
_index_shared_docs)
44+
from pandas.indexes.frozen import FrozenNDArray, FrozenList, _ensure_frozen
4545
import pandas.indexes.base as ibase
4646
_index_doc_kwargs = dict(ibase._index_doc_kwargs)
4747
_index_doc_kwargs.update(
@@ -1276,7 +1276,7 @@ def _assert_take_fillable(self, values, indices, allow_fill=True,
12761276
for new_label in taken:
12771277
label_values = new_label.values()
12781278
label_values[mask] = na_value
1279-
masked.append(base.FrozenNDArray(label_values))
1279+
masked.append(FrozenNDArray(label_values))
12801280
taken = masked
12811281
else:
12821282
taken = [lab.take(indices) for lab in self.labels]

pandas/tests/indexes/test_frozen.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import numpy as np
2+
from pandas.util import testing as tm
3+
from pandas.tests.test_base import CheckImmutable, CheckStringMixin
4+
from pandas.indexes.frozen import FrozenList, FrozenNDArray
5+
from pandas.compat import u
6+
7+
8+
class TestFrozenList(CheckImmutable, CheckStringMixin, tm.TestCase):
9+
mutable_methods = ('extend', 'pop', 'remove', 'insert')
10+
unicode_container = FrozenList([u("\u05d0"), u("\u05d1"), "c"])
11+
12+
def setUp(self):
13+
self.lst = [1, 2, 3, 4, 5]
14+
self.container = FrozenList(self.lst)
15+
self.klass = FrozenList
16+
17+
def test_add(self):
18+
result = self.container + (1, 2, 3)
19+
expected = FrozenList(self.lst + [1, 2, 3])
20+
self.check_result(result, expected)
21+
22+
result = (1, 2, 3) + self.container
23+
expected = FrozenList([1, 2, 3] + self.lst)
24+
self.check_result(result, expected)
25+
26+
def test_inplace(self):
27+
q = r = self.container
28+
q += [5]
29+
self.check_result(q, self.lst + [5])
30+
# other shouldn't be mutated
31+
self.check_result(r, self.lst)
32+
33+
34+
class TestFrozenNDArray(CheckImmutable, CheckStringMixin, tm.TestCase):
35+
mutable_methods = ('put', 'itemset', 'fill')
36+
unicode_container = FrozenNDArray([u("\u05d0"), u("\u05d1"), "c"])
37+
38+
def setUp(self):
39+
self.lst = [3, 5, 7, -2]
40+
self.container = FrozenNDArray(self.lst)
41+
self.klass = FrozenNDArray
42+
43+
def test_shallow_copying(self):
44+
original = self.container.copy()
45+
self.assertIsInstance(self.container.view(), FrozenNDArray)
46+
self.assertFalse(isinstance(
47+
self.container.view(np.ndarray), FrozenNDArray))
48+
self.assertIsNot(self.container.view(), self.container)
49+
self.assert_numpy_array_equal(self.container, original)
50+
# shallow copy should be the same too
51+
self.assertIsInstance(self.container._shallow_copy(), FrozenNDArray)
52+
53+
# setting should not be allowed
54+
def testit(container):
55+
container[0] = 16
56+
57+
self.check_mutable_error(testit, self.container)
58+
59+
def test_values(self):
60+
original = self.container.view(np.ndarray).copy()
61+
n = original[0] + 15
62+
vals = self.container.values()
63+
self.assert_numpy_array_equal(original, vals)
64+
self.assertIsNot(original, vals)
65+
vals[0] = n
66+
self.assertIsInstance(self.container, FrozenNDArray)
67+
self.assert_numpy_array_equal(self.container.values(), original)
68+
self.assertEqual(vals[0], n)

0 commit comments

Comments
 (0)