从一个命名元组基类继承

43

这个问题要求的是在Python中从基类继承一个命名元组的相反情况,也就是从一个命名元组派生一个子类而不是反过来。

在普通的继承中这样做是可以的:

class Y(object):
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c


class Z(Y):
    def __init__(self, a, b, c, d):
        super(Z, self).__init__(a, b, c)
        self.d = d
>>> Z(1,2,3,4)
<__main__.Z object at 0x10fcad950>

但如果基类是一个namedtuple

from collections import namedtuple

X = namedtuple('X', 'a b c')

class Z(X):
    def __init__(self, a, b, c, d):
        super(Z, self).__init__(a, b, c)
        self.d = d
>>> Z(1,2,3,4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __new__() takes exactly 4 arguments (5 given)

问题是,Python中是否可以将命名元组作为基类进行继承?如果可以,怎么做?


5
这并不完全是对你问题的回答,但可能值得查看一下新的Python 数据类。在大多数情况下,当你想要重写一个namedtuple时,你可能会考虑使用它们。 - ethanabrooks
4个回答

48

你可以这样做,但你需要重写__new__方法,因为它会在调用__init__方法之前被隐式调用:

class Z(X):
  def __new__(cls, a, b, c, d):
    self = super(Z, cls).__new__(cls, a, b, c)
    self.d = d
    return self

>>> z = Z(1, 2, 3, 4)
>>> z
Z(a=1, b=2, c=3)
>>> z.d
4

但是d将只是一个独立的属性!

>>> list(z)
[1, 2, 3]

我可以使用 self.append(d) 吗? - alvas
@alvas 不行,因为namedtuple基本上是一个tuple(设计上是不可变的,因此没有append)。 - MSeifert
@Mseifert 谢谢!但是 =( - alvas
5
如果我只需要几个额外的参数并希望将它们与其他参数分开,而且想在__init__方法中进行计算而不是在用户代码中分配,那么从namedtuple继承是否是反模式或者是一个合理的做法? - max
我刚刚考虑过继承namedtuple,因为我想要一个没有额外自定义方法但支持对字段值进行迭代的数据类。我不知道这是否是一种反模式,但这确实是一个真实世界的使用案例。 - undefined

16

我认为你可以通过将原始命名元组中的所有字段都包含在内,然后使用 schwobaseggl 上面建议的 __new__ 调整参数数量来实现你想要的目标。例如,为了解决 max 的情况,其中一些输入值是需要计算而不是直接提供的,以下方法可以解决:

from collections import namedtuple

class A(namedtuple('A', 'a b c computed_value')):
    def __new__(cls, a, b, c):
        computed_value = (a + b + c)
        return super(A, cls).__new__(cls, a, b, c, computed_value)

>>> A(1,2,3)
A(a=1, b=2, c=3, computed_value=6)

9

我和你一样也遇到了同样的问题,只不过是晚了两年。
个人认为@property装饰器更适用于这里:

from collections import namedtuple

class Base:
    @property
    def computed_value(self):
        return self.a + self.b + self.c

# inherits from Base
class A(Base, namedtuple('A', 'a b c')):
    pass

cls = A(1, 2, 3)
print(cls.computed_value)
# 6

1
现在文档中已经提到了这一点:https://docs.python.org/3/library/collections.html#collections.somenamedtuple._field_defaults - ggguser

0

不要严格按照继承的思路来考虑,因为 namedtuple 是一个函数,另一种方法是将其封装在一个新函数中。

那么问题就变成了:“如何构造一个具有默认属性 a,b,c,并可选一些其他属性的命名元组?”

def namedtuple_with_abc(name, props=[]):
     added = props if type(props) == type([]) else props.split()
     return namedtuple(name, ['a', 'b', 'c'] + added)

X = namedtuple_with_abc('X')
Z = namedtuple_with_abc('Z', 'd e')

>>> X(1, 2, 3)
X(a=1, b=2, c=3)

>>> Z(4, 5, 6, 7, 8)
Z(a=4, b=5, c=6, e=7, f=8)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接