13

Итак, у меня есть большое количество сообщений классов полезной нагрузки для последовательного API, каждый из которых имеет ряд неизменяемых полей, метод анализа и некоторые общие методы. Способ, которым я структурирую это, состоит в том, что каждый из них будет наследовать от namedtuple для поведения полей и получать общие методы из родительского класса. Тем не менее, у меня возникают некоторые трудности с конструкторами:В Python, как я могу назвать суперкласс, когда это одноразовый 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' для экономии памяти, вам необходимо установить' __slots__ =() 'в производном классе, а также другой унаследованного класса' Payload' или класса все равно будет '__dict__'. –

ответ

26

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

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

+0

О, ладно. Очень интересно. Для меня не имеет значения, как передать вещи в названный набор; Я просто хотел убедиться, что он не собирается пропустить свои аргументы. Возможно, я могу просто игнорировать всю проблему super(). – mikepurvis

+0

Хорошо, собираюсь принять этот, за то, что он полезен и краток. Думаю, в конце концов, я, вероятно, поеду с общим конструктором в полезной нагрузке, который вызывает полиморфный «self.verify()», который затем может генерировать исключения по мере необходимости. – mikepurvis

5

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

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

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

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

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

+0

Ваше утверждение «Правильная вещь в этом случае почти наверняка должна удалить аргумент' ** kwargs' и просто вызвать 'super (DifferentialSpeed, self) .__ init __ (** kwargs)' "кажется противоречивым, не следует последняя часть должна быть «просто вызывать» супер (DifferentialSpeed, self) .__ init __() '"? – martineau

+0

@martineau: Конечно, просто опечатка. –

+1

В случае, если кто-то еще сюда попадет, обратите внимание, что есть еще одна причина, связанная с этим предупреждением, показывающая или нет; см. комментарий о '__new__' в Object/typeobject.c. (Мне не хочется вдаваться в подробности, так как ОП полностью игнорировал этот ответ и принял тот, который даже не ответил на его вопрос ...) –

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) 
+0

Не нужно прикасаться к '__new__'. Ему нужно было бы только это сделать, если он хотел изменить аргументы, которые инициализируется 'DifferentialSpeed_'; он только проверяет их. –

+0

@Glenn Maynard: Мне кажется, что '__new __()' * * нужно было бы задействовать, чтобы аргументы могли быть проверены * до того, как они были назначены кортежу (поскольку их нельзя изменить позже). например Им могут быть присвоены значения по умолчанию или исключение, поднятое без каких-либо фиктивных присвоений. – martineau

+1

Выполнение исключения из '__init__' работает так же хорошо. (Тихая принудительная настройка по умолчанию не похожа на нормальное поведение.) –