15

所以,我有大量用于串行 API 的消息负载类,每个类都有一些不可变字段、一个解析方法和一些共享的方法。我构建它的方式是,每个都将从一个命名元组继承字段行为,并从父类接收公共方法。但是,我在构造函数方面遇到了一些困难:

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从通话中删除,它似乎仍然有效,但为什么呢?构造函数的这些参数是如何传递给命名元组的?这是有保证的,还是mro如何建立的随机结果?

如果我想远离超级,并以旧方式做,有什么方法可以访问 namedtuple 来调用它的构造函数吗?我宁愿不必这样做:

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

似乎有点冗长和不必要。

我在这里最好的做法是什么?

4

3 回答 3

28

对于初学者来说,namedtuple(whatever)继承自tuple,它是不可变的,不可变类型不会打扰__init__,因为在__init__被调用的时候对象已经被构造了。如果要将参数传递给namedtuple基类,则必须改写__new__

你可以namedtuple()通过传入一个verbose=true参数来查看结果的定义;我觉得它很有教育意义。

于 2010-11-01T18:17:00.960 回答
5

您有三个基类:Payload、您的 namedtupleDifferentialSpeed_和公共基类object。前两个都没有任何__init__功能,除了继承自object. namedtuple不需要__init__,因为不可变类的初始化是由在运行__new__之前调用的 完成的__init__

由于super(DifferentialSpeed, self).__init__解析为__init__调用链中的下一个,因此下一个__init__object.__init__,这意味着您正在将参数传递给该函数。它不期望任何 - 没有理由将参数传递给object.__init__.

(它曾经接受并默默地忽略参数。这种行为正在消失——它在 Python 3 中消失了——这就是你得到 DeprecationWarning 的原因。)

Payload.__init__您可以通过添加不带参数的函数来更清楚地触发问题。当您尝试传递 `* kwargs时,它会引发错误。

在这种情况下,正确的做法几乎可以肯定是删除**kwargs参数,然后调用super(DifferentialSpeed, self).__init__(). 它不需要任何参数;DifferentialSpeed正在传递Payload自己的参数,这些参数Payload和函数在调用链的下游一无所知。

于 2010-11-01T18:26:26.017 回答
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)
于 2010-11-01T20:40:43.503 回答