Python переопределяет __init__

Я знаю, что многие люди задают вопросы по этому вопросу, но я не видел, чтобы мой конкретный спросил. При подклассе вы можете переопределить __init__() так же, как вы можете переопределить любой другой метод. Мой вопрос в том, почему в приведенном ниже примере это не работает корректно:

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

Помните, что конструктор Random имеет следующую подпись: __init__(self, x=None) где x - необязательное семя. Я хочу сохранить эту функцию в моем подклассе, но также хочу потребовать две обязательные переменные, myvar1 и myvar2.

Однако, когда вы пытаетесь создать экземпляр (без семени), вы получите сообщение об ошибке:

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

Это связано с тем, что python считает, что вы хотите конструктор Random и передаете ваши два аргумента "var1" и "var2" в качестве семени. Семя (вызываемое изнутри конструктора Random) требует только 1 аргумент, и поэтому вы получаете сообщение об ошибке.

Однако, если вы делаете

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

Это работает, здесь python понимает, что вы передаете ему свои две обязательные переменные и не передаете ему необязательное семя.

Но я думаю, что первый случай тоже должен работать. Что происходит?

Ответ 1

В Python вызывается два метода при создании объекта. __new__ и __init__. Как и многие классы, реализованные в C, random.Random использует __new__ для инициализации (см. random_new). Вы должны перезаписать его и вызвать его с помощью соответствующих параметров:

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 ...)

Ответ 2

Вы неправильно определили проблему. Проблема в том, что инициализация random.Random не полностью содержится в random.Random.__init__. Он также наследует _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;
}

Вам также придется переопределить __new__ и передать только аргумент seed, который он ожидает, позиционно (потому что он не понимает его как аргумент ключевого слова).

Они действительно не должны смешивать __init__ и __new__ следующим образом. Порядок инициализации становится действительно странным, когда вы это делаете.