Skip to content

Commit 8fe1cf6

Browse files
hohjreback
authored andcommitted
FIX: Describe NotImplementedErrors or use AbstractMethodError
This issue fixes pandas-dev#7872 by either describing the reason why a NotImplementedError is raised, or by throwing an AbstractMethodError in the case of abstract methods (eg: Mixins) that should be overwritten by the inheriting classes. This issue is based on discussion of PR pandas-dev#7899 which was never merged.
1 parent 74f7c26 commit 8fe1cf6

20 files changed

+108
-72
lines changed

pandas/core/base.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import pandas.lib as lib
1212
from pandas.util.decorators import Appender, cache_readonly
1313
from pandas.core.strings import StringMethods
14-
14+
from pandas.core.common import AbstractMethodError
1515

1616
_shared_docs = dict()
1717
_indexops_doc_kwargs = dict(klass='IndexOpsMixin', inplace='',
@@ -32,7 +32,7 @@ class StringMixin(object):
3232
# Formatting
3333

3434
def __unicode__(self):
35-
raise NotImplementedError
35+
raise AbstractMethodError(self)
3636

3737
def __str__(self):
3838
"""
@@ -566,4 +566,4 @@ def duplicated(self, take_last=False):
566566
# abstracts
567567

568568
def _update_inplace(self, result, **kwargs):
569-
raise NotImplementedError
569+
raise AbstractMethodError(self)

pandas/core/categorical.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1166,7 +1166,8 @@ def fillna(self, fill_value=None, method=None, limit=None):
11661166
if fill_value is None:
11671167
fill_value = np.nan
11681168
if limit is not None:
1169-
raise NotImplementedError
1169+
raise NotImplementedError("specifying a limit for fillna has not "
1170+
"been implemented yet")
11701171

11711172
values = self._codes
11721173

pandas/core/common.py

+11
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ class AmbiguousIndexError(PandasError, KeyError):
3939
pass
4040

4141

42+
class AbstractMethodError(NotImplementedError):
43+
"""Raise this error instead of NotImplementedError for abstract methods
44+
while keeping compatibility with Python 2 and Python 3.
45+
"""
46+
def __init__(self, class_instance):
47+
self.class_instance = class_instance
48+
49+
def __str__(self):
50+
return "This method must be defined on the concrete class of " \
51+
+ self.class_instance.__class__.__name__
52+
4253
_POSSIBLY_CAST_DTYPES = set([np.dtype(t).name
4354
for t in ['O', 'int8',
4455
'uint8', 'int16', 'uint16', 'int32',

pandas/core/generic.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
from pandas.core.common import (isnull, notnull, is_list_like,
2222
_values_from_object, _maybe_promote,
2323
_maybe_box_datetimelike, ABCSeries,
24-
SettingWithCopyError, SettingWithCopyWarning)
24+
SettingWithCopyError, SettingWithCopyWarning,
25+
AbstractMethodError)
2526
import pandas.core.nanops as nanops
2627
from pandas.util.decorators import Appender, Substitution, deprecate_kwarg
2728
from pandas.core import config
@@ -137,7 +138,7 @@ def _init_mgr(self, mgr, axes=None, dtype=None, copy=False):
137138

138139
@property
139140
def _constructor(self):
140-
raise NotImplementedError
141+
raise AbstractMethodError(self)
141142

142143
def __unicode__(self):
143144
# unicode representation based upon iterating over self
@@ -152,7 +153,7 @@ def _local_dir(self):
152153

153154
@property
154155
def _constructor_sliced(self):
155-
raise NotImplementedError
156+
raise AbstractMethodError(self)
156157

157158
#----------------------------------------------------------------------
158159
# Axis
@@ -1100,7 +1101,7 @@ def _iget_item_cache(self, item):
11001101
return lower
11011102

11021103
def _box_item_values(self, key, values):
1103-
raise NotImplementedError
1104+
raise AbstractMethodError(self)
11041105

11051106
def _maybe_cache_changed(self, item, value):
11061107
"""
@@ -3057,7 +3058,8 @@ def first(self, offset):
30573058
"""
30583059
from pandas.tseries.frequencies import to_offset
30593060
if not isinstance(self.index, DatetimeIndex):
3060-
raise NotImplementedError
3061+
raise NotImplementedError("'first' only supports a DatetimeIndex "
3062+
"index")
30613063

30623064
if len(self.index) == 0:
30633065
return self
@@ -3091,7 +3093,8 @@ def last(self, offset):
30913093
"""
30923094
from pandas.tseries.frequencies import to_offset
30933095
if not isinstance(self.index, DatetimeIndex):
3094-
raise NotImplementedError
3096+
raise NotImplementedError("'last' only supports a DatetimeIndex "
3097+
"index")
30953098

30963099
if len(self.index) == 0:
30973100
return self

pandas/core/groupby.py

+20-15
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
notnull, _DATELIKE_DTYPES, is_numeric_dtype,
2626
is_timedelta64_dtype, is_datetime64_dtype,
2727
is_categorical_dtype, _values_from_object,
28-
is_datetime_or_timedelta_dtype, is_bool_dtype)
28+
is_datetime_or_timedelta_dtype, is_bool_dtype,
29+
AbstractMethodError)
2930
from pandas.core.config import option_context
3031
import pandas.lib as lib
3132
from pandas.lib import Timestamp
@@ -279,7 +280,7 @@ def _set_grouper(self, obj, sort=False):
279280
return self.grouper
280281

281282
def _get_binner_for_grouping(self, obj):
282-
raise NotImplementedError
283+
raise AbstractMethodError(self)
283284

284285
@property
285286
def groups(self):
@@ -670,7 +671,7 @@ def _python_apply_general(self, f):
670671
not_indexed_same=mutated)
671672

672673
def aggregate(self, func, *args, **kwargs):
673-
raise NotImplementedError
674+
raise AbstractMethodError(self)
674675

675676
@Appender(_agg_doc)
676677
def agg(self, func, *args, **kwargs):
@@ -680,7 +681,7 @@ def _iterate_slices(self):
680681
yield self.name, self._selected_obj
681682

682683
def transform(self, func, *args, **kwargs):
683-
raise NotImplementedError
684+
raise AbstractMethodError(self)
684685

685686
def mean(self):
686687
"""
@@ -1127,7 +1128,7 @@ def _python_agg_general(self, func, *args, **kwargs):
11271128
return self._wrap_aggregated_output(output)
11281129

11291130
def _wrap_applied_output(self, *args, **kwargs):
1130-
raise NotImplementedError
1131+
raise AbstractMethodError(self)
11311132

11321133
def _concat_objects(self, keys, values, not_indexed_same=False):
11331134
from pandas.tools.merge import concat
@@ -1484,7 +1485,8 @@ def aggregate(self, values, how, axis=0):
14841485
swapped = True
14851486
values = values.swapaxes(0, axis)
14861487
if arity > 1:
1487-
raise NotImplementedError
1488+
raise NotImplementedError("arity of more than 1 is not "
1489+
"supported for the 'how' argument")
14881490
out_shape = (self.ngroups,) + values.shape[1:]
14891491

14901492
is_numeric = is_numeric_dtype(values.dtype)
@@ -1556,7 +1558,8 @@ def _aggregate(self, result, counts, values, agg_func, is_numeric):
15561558
comp_ids, _, ngroups = self.group_info
15571559
if values.ndim > 3:
15581560
# punting for now
1559-
raise NotImplementedError
1561+
raise NotImplementedError("number of dimensions is currently "
1562+
"limited to 3")
15601563
elif values.ndim > 2:
15611564
for i, chunk in enumerate(values.transpose(2, 0, 1)):
15621565

@@ -1815,7 +1818,8 @@ def _aggregate(self, result, counts, values, agg_func, is_numeric=True):
18151818

18161819
if values.ndim > 3:
18171820
# punting for now
1818-
raise NotImplementedError
1821+
raise NotImplementedError("number of dimensions is currently "
1822+
"limited to 3")
18191823
elif values.ndim > 2:
18201824
for i, chunk in enumerate(values.transpose(2, 0, 1)):
18211825
agg_func(result[:, :, i], counts, chunk, self.bins)
@@ -2622,7 +2626,8 @@ def aggregate(self, arg, *args, **kwargs):
26222626
if self._selection is not None:
26232627
subset = obj
26242628
if isinstance(subset, DataFrame):
2625-
raise NotImplementedError
2629+
raise NotImplementedError("Aggregating on a DataFrame is "
2630+
"not supported")
26262631

26272632
for fname, agg_how in compat.iteritems(arg):
26282633
colg = SeriesGroupBy(subset, selection=self._selection,
@@ -2671,7 +2676,7 @@ def _aggregate_multiple_funcs(self, arg):
26712676
from pandas.tools.merge import concat
26722677

26732678
if self.axis != 0:
2674-
raise NotImplementedError
2679+
raise NotImplementedError("axis other than 0 is not supported")
26752680

26762681
obj = self._obj_with_exclusions
26772682

@@ -2721,7 +2726,7 @@ def _aggregate_generic(self, func, *args, **kwargs):
27212726
return self._wrap_generic_output(result, obj)
27222727

27232728
def _wrap_aggregated_output(self, output, names=None):
2724-
raise NotImplementedError
2729+
raise AbstractMethodError(self)
27252730

27262731
def _aggregate_item_by_item(self, func, *args, **kwargs):
27272732
# only for axis==0
@@ -3283,7 +3288,7 @@ def _iterate_slices(self):
32833288
slice_axis = self._selection_list
32843289
slicer = lambda x: self._selected_obj[x]
32853290
else:
3286-
raise NotImplementedError
3291+
raise NotImplementedError("axis other than 0 is not supported")
32873292

32883293
for val in slice_axis:
32893294
if val in self.exclusions:
@@ -3348,10 +3353,10 @@ def _aggregate_item_by_item(self, func, *args, **kwargs):
33483353
new_axes[self.axis] = self.grouper.result_index
33493354
return Panel._from_axes(result, new_axes)
33503355
else:
3351-
raise NotImplementedError
3356+
raise ValueError("axis value must be greater than 0")
33523357

33533358
def _wrap_aggregated_output(self, output, names=None):
3354-
raise NotImplementedError
3359+
raise AbstractMethodError(self)
33553360

33563361

33573362
class NDArrayGroupBy(GroupBy):
@@ -3405,7 +3410,7 @@ def _chop(self, sdata, slice_obj):
34053410
return sdata.iloc[slice_obj]
34063411

34073412
def apply(self, f):
3408-
raise NotImplementedError
3413+
raise AbstractMethodError(self)
34093414

34103415

34113416
class ArraySplitter(DataSplitter):

pandas/core/internals.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,8 @@ def fillna(self, value, limit=None, inplace=False, downcast=None):
294294
mask = isnull(self.values)
295295
if limit is not None:
296296
if self.ndim > 2:
297-
raise NotImplementedError
297+
raise NotImplementedError("number of dimensions for 'fillna' "
298+
"is currently limited to 2")
298299
mask[mask.cumsum(self.ndim-1)>limit]=False
299300

300301
value = self._try_fill(value)
@@ -1681,7 +1682,8 @@ def _slice(self, slicer):
16811682
def fillna(self, value, limit=None, inplace=False, downcast=None):
16821683
# we may need to upcast our fill to match our dtype
16831684
if limit is not None:
1684-
raise NotImplementedError
1685+
raise NotImplementedError("specifying a limit for 'fillna' has "
1686+
"not been implemented yet")
16851687

16861688
values = self.values if inplace else self.values.copy()
16871689
return [self.make_block_same_class(values=values.fillna(fill_value=value,
@@ -1848,7 +1850,8 @@ def fillna(self, value, limit=None,
18481850
value = self._try_fill(value)
18491851
if limit is not None:
18501852
if self.ndim > 2:
1851-
raise NotImplementedError
1853+
raise NotImplementedError("number of dimensions for 'fillna' "
1854+
"is currently limited to 2")
18521855
mask[mask.cumsum(self.ndim-1)>limit]=False
18531856

18541857
np.putmask(values, mask, value)
@@ -2011,7 +2014,8 @@ def interpolate(self, method='pad', axis=0, inplace=False,
20112014
def fillna(self, value, limit=None, inplace=False, downcast=None):
20122015
# we may need to upcast our fill to match our dtype
20132016
if limit is not None:
2014-
raise NotImplementedError
2017+
raise NotImplementedError("specifying a limit for 'fillna' has "
2018+
"not been implemented yet")
20152019
if issubclass(self.dtype.type, np.floating):
20162020
value = float(value)
20172021
values = self.values if inplace else self.values.copy()

pandas/core/panelnd.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def _combine_with_constructor(self, other, func):
9999
for f in ['to_frame', 'to_excel', 'to_sparse', 'groupby', 'join', 'filter',
100100
'dropna', 'shift']:
101101
def func(self, *args, **kwargs):
102-
raise NotImplementedError
102+
raise NotImplementedError("this operation is not supported")
103103
setattr(klass, f, func)
104104

105105
# add the aggregate operations

pandas/core/series.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ def __init__(self, data=None, index=None, dtype=None, name=None,
140140
dtype = self._validate_dtype(dtype)
141141

142142
if isinstance(data, MultiIndex):
143-
raise NotImplementedError
143+
raise NotImplementedError("initializing a Series from a "
144+
"MultiIndex is not supported")
144145
elif isinstance(data, Index):
145146
# need to copy to avoid aliasing issues
146147
if name is None:

pandas/io/html.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
raise_with_traceback, binary_type)
2020
from pandas.core import common as com
2121
from pandas import Series
22+
from pandas.core.common import AbstractMethodError
2223

2324
_IMPORTS = False
2425
_HAS_BS4 = False
@@ -229,7 +230,7 @@ def _text_getter(self, obj):
229230
text : str or unicode
230231
The text from an individual DOM node.
231232
"""
232-
raise NotImplementedError
233+
raise AbstractMethodError(self)
233234

234235
def _parse_td(self, obj):
235236
"""Return the td elements from a row element.
@@ -243,7 +244,7 @@ def _parse_td(self, obj):
243244
columns : list of node-like
244245
These are the elements of each row, i.e., the columns.
245246
"""
246-
raise NotImplementedError
247+
raise AbstractMethodError(self)
247248

248249
def _parse_tables(self, doc, match, attrs):
249250
"""Return all tables from the parsed DOM.
@@ -270,7 +271,7 @@ def _parse_tables(self, doc, match, attrs):
270271
tables : list of node-like
271272
A list of <table> elements to be parsed into raw data.
272273
"""
273-
raise NotImplementedError
274+
raise AbstractMethodError(self)
274275

275276
def _parse_tr(self, table):
276277
"""Return the list of row elements from the parsed table element.
@@ -285,7 +286,7 @@ def _parse_tr(self, table):
285286
rows : list of node-like
286287
A list row elements of a table, usually <tr> or <th> elements.
287288
"""
288-
raise NotImplementedError
289+
raise AbstractMethodError(self)
289290

290291
def _parse_thead(self, table):
291292
"""Return the header of a table.
@@ -300,7 +301,7 @@ def _parse_thead(self, table):
300301
thead : node-like
301302
A <thead>...</thead> element.
302303
"""
303-
raise NotImplementedError
304+
raise AbstractMethodError(self)
304305

305306
def _parse_tbody(self, table):
306307
"""Return the body of the table.
@@ -315,7 +316,7 @@ def _parse_tbody(self, table):
315316
tbody : node-like
316317
A <tbody>...</tbody> element.
317318
"""
318-
raise NotImplementedError
319+
raise AbstractMethodError(self)
319320

320321
def _parse_tfoot(self, table):
321322
"""Return the footer of the table if any.
@@ -330,7 +331,7 @@ def _parse_tfoot(self, table):
330331
tfoot : node-like
331332
A <tfoot>...</tfoot> element.
332333
"""
333-
raise NotImplementedError
334+
raise AbstractMethodError(self)
334335

335336
def _build_doc(self):
336337
"""Return a tree-like object that can be used to iterate over the DOM.
@@ -339,7 +340,7 @@ def _build_doc(self):
339340
-------
340341
obj : tree-like
341342
"""
342-
raise NotImplementedError
343+
raise AbstractMethodError(self)
343344

344345
def _build_table(self, table):
345346
header = self._parse_raw_thead(table)

0 commit comments

Comments
 (0)