В Python, как я могу назвать суперкласс, когда он является одноразовым namedtuple?

Итак, у меня есть большое количество классов полезной нагрузки для последовательного API, каждый из которых имеет ряд неизменяемых полей, метод синтаксического анализа и некоторые общие методы. Способ, которым я структурирую это, состоит в том, что каждый из них будет наследовать от namedtuple для поведения полей и получать общие методы из родительского класса. Однако у меня возникают трудности с конструкторами:

class Payload:
    def test(self):
        print("bar")

class DifferentialSpeed(Payload, namedtuple('DifferentialSpeed_', 
    'left_speed right_speed left_accel right_accel')):
    __slots__ = ()
    def __init__(self, **kwargs):
        super(DifferentialSpeed, self).__init__(**kwargs)
        # TODO: Field verification
        print("foo")

    @classmethod
    def parse(self, raw):
        # Dummy for now
        return self(left_speed = 0.0, right_speed = 0.1,
                    left_accel = 0.2, right_accel = 0.3)

    def __str__(self):
        return "Left Speed: %fm/s\nRight Speed: %fm/s\n"\
            "Left Acceleration: %fm/s^2\nRight Acceleration: %fm/s^2" % (
            self.left_speed, self.right_speed, self.left_accel, self.right_accel)


payload = DifferentialSpeed.parse('dummy')
print(payload)

Это работает, но я получаю следующее предупреждение:

DeprecationWarning: object.__init__() takes no parameters
  super(DifferentialSpeed, self).__init__(**kwargs)

Если я удалю **kwargs из вызова, он все равно работает, но почему? Как эти аргументы для конструктора передаются в namedtuple? Является ли это гарантированным или случайным результатом того, как устанавливается mro?

Если бы я хотел держаться подальше от супер и делать это по-старому, есть ли способ получить доступ к namedtuple, чтобы вызвать его конструктор? Я бы предпочел не делать этого:

DifferentialSpeed_ = namedtuple('DifferentialSpeed_', 
    'left_speed right_speed left_accel right_accel')
class DifferentialSpeed(Payload, DifferentialSpeed_):

Кажется, многословный и ненужный.

Какой мой лучший курс действий здесь?

Ответы

Ответ 1

Во-первых, namedtuple(whatever) наследует от tuple, который неизменен, а неизменяемые типы не беспокоят __init__, потому что к моменту времени __init__ вызывается, объект уже построен. Если вы хотите передать аргументы базовому классу namedtuple, вместо этого вам придется переопределить __new__.

Вы можете увидеть определение результата namedtuple(), передав аргумент verbose=true; Я нахожу это образовательным.

Ответ 2

У вас есть три базовых класса: Payload, ваш namedtuple DifferentialSpeed_ и общий базовый класс object. Ни один из первых двух не имеет функции __init__ вообще, кроме той, что унаследована от object. namedtuple не требуется __init__, так как инициализация неизменяемых классов выполняется с помощью __new__, который вызывается до запуска __init__.

Так как super(DifferentialSpeed, self).__init__ переходит к следующему __init__ в цепочке вызовов, следующий __init__ равен object.__init__, что означает, что вы передаете аргументы этой функции. Он не ожидает никаких - нет причин передавать аргументы object.__init__.

(Он использовал, чтобы принимать и молча игнорировать аргументы. Это поведение уходит - оно появилось в Python 3 - вот почему вы получаете сообщение об изнашивании.)

Вы можете более четко определить проблему, добавив функцию Payload.__init__, которая не принимает аргументов. Когда вы пытаетесь пройти вдоль `* kwargs, это вызовет ошибку.

Правильная вещь в этом случае - почти наверняка удалить аргумент **kwargs и просто вызвать super(DifferentialSpeed, self).__init__(). Он не принимает никаких аргументов; DifferentialSpeed передает Payload свои собственные аргументы, которые Payload и функционируют дальше по цепочке вызовов, ничего не знают о.

Ответ 3

Как указывали другие, кортежи являются неизменяемым типом, который должен быть инициализирован в их __new__() вместо их метода __init__() - поэтому вам нужно добавить первое в свой подкласс (и избавиться от последний). Ниже описано, как это применимо к вашему примеру кода. Единственное другое изменение заключалось в добавлении инструкции from import... в начало.

Примечание. cls должен быть передан дважды в вызове super() в __new__(), потому что это статический метод, хотя он имеет специальную обложку, поэтому вам не нужно его объявлять быть одним.

from collections import namedtuple

class Payload:
    def test(self):
        print("bar")

class DifferentialSpeed(Payload, namedtuple('DifferentialSpeed_',
    'left_speed right_speed left_accel right_accel')):
    #### NOTE: __new__ instead of an __init__ method ####
    def __new__(cls, **kwargs):
        self = super(DifferentialSpeed, cls).__new__(cls, **kwargs)
        # TODO: Field verification
        print("foo")
        return self

    @classmethod
    def parse(self, raw):
        # Dummy for now
        return self(left_speed = 0.0, right_speed = 0.1,
                    left_accel = 0.2, right_accel = 0.3)

    def __str__(self):
        return "Left Speed: %fm/s\nRight Speed: %fm/s\n"\
            "Left Acceleration: %fm/s^2\nRight Acceleration: %fm/s^2" % (
            self.left_speed, self.right_speed, self.left_accel, self.right_accel)


payload = DifferentialSpeed.parse('dummy')
print(payload)