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