Question: I am trying to set up a subclass of pd.DataFrame that has two required arguments when initializing (group and timestamp_col). I want to run validation on those arguments group and timestamp_col, so I have a setter method for each of the properties. This all works until I try to set_index() and get TypeError: 'NoneType' object is not iterable. It appears no argument is being passed to my setter function in test_set_index and test_assignment_with_indexed_obj. If I add if g == None: return to my setter function, I can pass the test cases but don't think that is the proper solution. How should I implement property validation for these required arguments? Below is my class: import pandas as pd import numpy as np class HistDollarGains(pd.DataFrame): @property def _constructor(self): return HistDollarGains._internal_ctor _metadata = ["group", "timestamp_col", "_group", "_timestamp_col"] @classmethod def _internal_ctor(cls, *args, **kwargs): kwargs["group"] = None kwargs["timestamp_col"] = None return cls(*args, **kwargs) def __init__( self, data, group, timestamp_col, index=None, columns=None, dtype=None, copy=True, ): super(HistDollarGains, self).__init__( data=data, index=index, columns=columns, dtype=dtype, copy=copy ) self.group = group self.timestamp_col = timestamp_col @property def group(self): return self._group @group.setter def group(self, g): if g == None: return if isinstance(g, str): group_list = [g] else: group_list = g if not set(group_list).issubset(self.columns): raise ValueError("Data does not contain " + '[' + ', '.join(group_list) + ']') self._group = group_list @property def timestamp_col(self): return self._timestamp_col @timestamp_col.setter def timestamp_col(self, t): if t == None: return if not t in self.columns: raise ValueError("Data does not contain " + '[' + t + ']') self._timestamp_col = t  Here are my test cases: import pytest import pandas as pd import numpy as np from myclass import * @pytest.fixture(scope="module") def sample(): samp = pd.DataFrame( [ {"timestamp": "2020-01-01", "group": "a", "dollar_gains": 100}, {"timestamp": "2020-01-01", "group": "b", "dollar_gains": 100}, {"timestamp": "2020-01-01", "group": "c", "dollar_gains": 110}, {"timestamp": "2020-01-01", "group": "a", "dollar_gains": 110}, {"timestamp": "2020-01-01", "group": "b", "dollar_gains": 90}, {"timestamp": "2020-01-01", "group": "d", "dollar_gains": 100}, ] ) return samp @pytest.fixture(scope="module") def sample_obj(sample): return HistDollarGains(sample, "group", "timestamp") def test_constructor_without_args(sample): with pytest.raises(TypeError): HistDollarGains(sample) def test_constructor_with_string_group(sample): hist_dg = HistDollarGains(sample, "group", "timestamp") assert hist_dg.group == ["group"] assert hist_dg.timestamp_col == "timestamp" def test_constructor_with_list_group(sample): hist_dg = HistDollarGains(sample, ["group", "timestamp"], "timestamp") def test_constructor_with_invalid_group(sample): with pytest.raises(ValueError): HistDollarGains(sample, "invalid_group", np.random.choice(sample.columns)) def test_constructor_with_invalid_timestamp(sample): with pytest.raises(ValueError): HistDollarGains(sample, np.random.choice(sample.columns), "invalid_timestamp") def test_assignment_with_indexed_obj(sample_obj): b = sample_obj.set_index(sample_obj.group + [sample_obj.timestamp_col]) def test_set_index(sample_obj): # print(isinstance(a, pd.DataFrame)) assert sample_obj.set_index(sample_obj.group + [sample_obj.timestamp_col]).index.names == ['group', 'timestamp'] 

Answer: The set_index() method will call self.copy() internally to create a copy of your DataFrame object (see the source code here), inside which it uses your customized constructor method, _internal_ctor(), to create the new object (source). Note that self._constructor() is identical to self._internal_ctor(), which is a common internal method for nearly all pandas classes for creating new instances during operations like deep-copy or slicing. Your problem actually originates from this function: class HistDollarGains(pd.DataFrame): ... @classmethod def _internal_ctor(cls, *args, **kwargs): kwargs["group"] = None kwargs["timestamp_col"] = None return cls(*args, **kwargs) # this is equivalent to calling # HistDollarGains(data, group=None, timestamp_col=None)  I guess you copied this code from the github issue. The lines kwargs["**"] = None explicitly tells the constructor to set None to both group and timestamp_col. Finally the setter/validator gets None as the new value and raise an error. Therefore, you should set an acceptable value to group and timestamp_col.  @classmethod def _internal_ctor(cls, *args, **kwargs): kwargs["group"] = [] kwargs["timestamp_col"] = 'timestamp' # or whatever name that makes your validator happy return cls(*args, **kwargs)  Then you can delete the if g == None: return lines in the validator.