user1950164
user1950164

Reputation:

Python overriding __init__

I know that plenty of people ask questions on this subject, but I haven't seen my specific one asked. When subclassing you can override __init__() the same way that you can override any other method. My question is why in the example below this doesn't seem to be working correctly:

import random
class MyRand(random.Random):
    def __init__(self, myvar1, myvar2, x=None):
        # ( ... my code ...)
        super(MyRand, self).__init__(x)

Remember that Random's constructor has the following signature: __init__(self, x=None) where x is an optional seed. I want to keep that feature in my subclass, but also I want to require two mandatory variables, myvar1 and myvar2.

However, when you try and instantiate (without a seed) you get an error:

MyRand('var1', 'var2')
TypeError: seed expected at most 1 arguments, got 2

This is because python thinks you want Random's constructor and passes your two arguments 'var1' and 'var2' as the seed. The seed (which is called from inside Random's constructor) only wants 1 argument, and so you get an error.

However, if you do

MyRand(myvar1='var1', myvar2='var2')

This works, here python understands that you're passing it your two mandatory variables and not passing it the optional seed.

But I think the first case should work too. What's going on?

Upvotes: 13

Views: 7155

Answers (2)

Daniel
Daniel

Reputation: 42768

In Python two methods are called when a object is created. __new__ and __init__. Like many classes implemented in C, random.Random uses __new__ to initialize itself (see random_new). You have to overwrite it and call it with the appropriate parameters:

import random

class MyRand(random.Random):
    def __new__(cls, myvar1, myvar2, x=None):
        return random.Random.__new__(cls, x)

    def __init__(self, myvar1, myvar2, x=None):
        # ( ... my code ...)

Upvotes: 11

user2357112
user2357112

Reputation: 281476

You've mis-diagnosed the problem a little. The problem is that random.Random's initialization isn't entirely contained in random.Random.__init__. It also inherits _random.Random.__new__:

static PyObject *
random_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    RandomObject *self;
    PyObject *tmp;

    if (type == &Random_Type && !_PyArg_NoKeywords("Random()", kwds))
        return NULL;

    self = (RandomObject *)type->tp_alloc(type, 0);
    if (self == NULL)
        return NULL;
    tmp = random_seed(self, args);
    if (tmp == NULL) {
        Py_DECREF(self);
        return NULL;
    }
    Py_DECREF(tmp);
    return (PyObject *)self;
}

You're going to have to override __new__, too, and only pass the seed argument it expects, positionally (because it doesn't understand it as a keyword argument).

They really shouldn't be mixing __init__ and __new__ like this. Initialization order gets really weird when you do that.

Upvotes: 8

Related Questions