Jonathan Bechtel
Jonathan Bechtel

Reputation: 3607

TypeError: __init__() got an unexpected keyword argument when class is initiated inside another one

I have the following class that works like this:

class DecisionTreeRegressor():
    def __init__(self, min_leaf=5, impurity_threshold=1e-5,
                 root=None, leaf_value=None, impurity=None):
        self.min_leaf = min_leaf
        self.impurity_threshold = impurity_threshold
        self.root = root
        self._leaf_calculation = leaf_value
        self._impurity_calculation = impurity

I would like to initiate the DecisionTreeRegressor class within another class called RandomForestRegressor, which currently has the following structure, for itself and its parent class RandomForest:

class RandomForest():
    def __init__(self, n_estimators=10, min_leaf=5, 
                 sample_size = 2/3, min_impurity=1e-5):
        self.n_estimators = n_estimators
        self.min_leaf = min_leaf
        self.sample_size = sample_size
        self.min_impurity = min_impurity

class RandomForestRegressor(RandomForest):
    def __init__(self):
        super().__init__() 
        self.tree = DecisionTreeRegressor
        self.trees = [self.tree(min_leaf=self.min_leaf) 
                      for i in range(self.n_estimators)]

And this returns the following error message:

TypeError: __init__() got an unexpected keyword argument 'min_leaf' 

I find this puzzling for the following reasons:

DecisionTreeRegressor(min_leaf=5)

Works just fine.

Also, if I change RandomForestRegressor to:

class RandomForestRegressor(RandomForest):
    def __init__(self):
        super().__init__() 
        self.tree = DecisionTreeRegressor
        self.trees = [self.tree() for i in range(self.n_estimators)]

This also works correctly, with self.trees being a list filled with n different instances of the DecisionTreeRegressor class.

Why is passing in the min_leaf argument invoking an error message like it is?

Upvotes: 1

Views: 12114

Answers (2)

Michael
Michael

Reputation: 216

A method or function, an instance of the method class, may define parameters. However, when the method is called, it can only take in the parameters it defined. For example:

def functionwithnoparameters():
        pass

The code below will work.

functionwithnoparameters()

But, the code below will raise a TypeError because the function expected no parameters.

functionwithnoparameters(1)

It was given too many parameters, more than it expected so it raised TypeError. You were mistaken that only excess or too little value arguments can raise TypeError. But, excess or too little keyword arguments can also cause TypeError per this example.

functionwithnoparameters(one=1)

But, DecisionTreeRegressor didn't define any keyword parameters so it coughed up TypeError when one was given. That's the reason for the TypeError.

There are two solutions to your problem.

1) The interpreter thinks min_leaf and the other parameters are value parameters not keyword parameters. Because min_leaf is the first parameter, you can just use the value for min_leaf!

2) The cleaner solution. Parameters in Python default to be value parameters, and the end of the value parameters is marked when a parameter proceeded by a *, a tuple of all additional value arguments. If you want to mark the end of the value parameters without enabling unlimited value arguments, use * as a plain parameter. When the end of value parameters happens, keyword parameters begin. Unlimited keyword parameters occour if a parameter is proceeded by **. This must be the last of the method's parameters.

Now, to rewrite the DecisionTreeRegressor class according to the second solution:

class DecisionTreeRegressor():

    def __init__(self, *, min_leaf=5, impurity_threshold=1e-5, root=None, leaf_value=None, impurity=None):

        self.min_leaf              = min_leaf
        self.impurity_threshold    = impurity_threshold
        self.root                  = root
        self._leaf_calculation     = leaf_value
        self._impurity_calculation = impurity

The little * before the keyword arguments completely solves the problem. The first solution is only a bandaid solution.

I feel dissatisfied with Jeff Mercado's answer because the TypeError occours because of lack of support rooted in the DecisionTreeRegressor class, not due to the lack of parameters in the RandomForestRegressor class' constructor.

However, I would highly reccommend that RandomForestRegressor's constructor should take in RandomForest's parameters and call RandomForest's constructor with those parameters, not with its default parameters. They can be value or keyword arguments, whatever you would like, now that I told you how to implement both.

Upvotes: 1

Jeff Mercado
Jeff Mercado

Reputation: 134891

I'm assuming you're calling this:

RandomForestRegressor(min_leaf=5)

The initializer for the RandomForestRegressor class has no declared keyword arguments, it takes none at all.

You'll either need to explicitly add them in or add kwargs to the signature.

class RandomForestRegressor(RandomForest):
    def __init__(self, **kwargs):
        super().__init__(**kwargs) 

Upvotes: 1

Related Questions