如何在多重继承中使用命名元组(namedtuples)

3

是否可以创建一个继承自多个namedtuple实例的类,或者创建类似的东西(具有将基本类型字段组合在一起的不可变类型)?我还没有找到这样做的方法。

下面的示例说明了问题:

>>> class Test(namedtuple('One', 'foo'), namedtuple('Two', 'bar')):
>>>    pass

>>> t = Test(1, 2)
TypeError: __new__() takes 2 positional arguments but 3 were given

>>> t = Test(1)
>>> t.foo
1
>>> t.bar
1

问题似乎是由于namedtuple在创建时未使用super来初始化其基类引起的,可以通过以下方式验证:
>>> namedtuple('Test', ('field'), verbose=True)
[...]    
class Test(tuple):
[...]
    def __new__(_cls, field,):
        'Create new instance of Test(field,)'
        return _tuple.__new__(_cls, (field,))

即使我考虑编写自己版本的namedtuple来解决这个问题,但如何实现并不明显。如果一个类的MRO中有多个namedtuple实例,则它们必须共享单个基类tuple的实例。为了做到这一点,它们必须协调哪个namedtuple使用基元组中的哪个索引范围。是否有更简单的方法来实现带有namedtuple或类似物的多重继承?是否已经有人在某个地方实现了它?

如果超类包含不同数量的字段或不同的字段名称,您将如何提出解决歧义的建议? - Joel Cornett
我不理解这个问题。我期望所有的行为都与常规可变类相同,只是它是不可变的。子类将拥有所有基类的字段。因此,上面的示例等同于namedtuple(Test, ('foo', 'bar')) - Björn Pollex
1
问题在于,由于您没有定义 Test.__init__,因此只调用了第一个基类的 __init__ 函数,并且该函数仅接受一个(附加的)参数。 - chepner
3个回答

6

你可以使用装饰器或元类将父命名元组字段组合成一个新的命名元组,并将其添加到类的__bases__中:

from collections import namedtuple

def merge_fields(cls):
    name = cls.__name__
    bases = cls.__bases__

    fields = []
    for c in bases:
        if not hasattr(c, '_fields'):
            continue
        fields.extend(f for f in c._fields if f not in fields)

    if len(fields) == 0:
        return cls

    combined_tuple = namedtuple('%sCombinedNamedTuple' % name, fields)
    return type(name, (combined_tuple,) + bases, dict(cls.__dict__))


class SomeParent(namedtuple('Two', 'bar')):

    def some_parent_meth(self):
        return 'method from SomeParent'


class SomeOtherParent(object):

    def __init__(self, *args, **kw):
        print 'called from SomeOtherParent.__init__ with', args, kw

    def some_other_parent_meth(self):
        return 'method from SomeOtherParent'


@merge_fields
class Test(namedtuple('One', 'foo'), SomeParent, SomeOtherParent):

    def some_method(self):
        return 'do something with %s' % (self,)


print Test.__bases__
# (
#   <class '__main__.TestCombinedNamedTuple'>, <class '__main__.One'>, 
#   <class '__main__.SomeParent'>, <class '__main__.SomeOtherParent'>
# )
t = Test(1, 2)  # called from SomeOtherParent.__init__ with (1, 2) {} 
print t  # Test(foo=1, bar=2)
print t.some_method()  # do something with Test(foo=1, bar=2)
print t.some_parent_meth()  # method from SomeParent
print t.some_other_parent_meth()  # method from SomeOtherParent

5
这段代码采用了与Francis Colas类似的方法,尽管略微冗长 :)
这是一个工厂函数,它接受任意数量的父命名元组,并创建一个新的命名元组,其中包含所有父元组中的字段,按顺序排列,跳过任何重复的字段名。
from collections import namedtuple

def combined_namedtuple(typename, *parents):
    #Gather fields, in order, from parents, skipping dupes
    fields = []
    for t in parents:
        for f in t._fields:
            if f not in fields:
                fields.append(f)
    return namedtuple(typename, fields)

nt1 = namedtuple('One', ['foo', 'qux'])
nt2 = namedtuple('Two', ['bar', 'baz'])    

Combo = combined_namedtuple('Combo', nt1, nt2)    
ct = Combo(1, 2, 3, 4)
print ct

输出

Combo(foo=1, qux=2, bar=3, baz=4)

3

如果你只想要一个带有这两个字段的namedtuple,那么重新创建它就很容易:

One = namedtuple('One', 'foo')
Two = namedtuple('Two', 'bar')
Test = namedtuple('Test', One._fields+Two._fields)

那个例子在我提供的情况下可以工作,但实际情况稍微复杂一些。在我的真实用例中,我有一个类直接和间接继承自 namedtuple。显然,我过于简化了我的示例。 - Björn Pollex
好的,你能否重写一个不那么简洁的例子?因为我不确定在这种情况下组合是否更好。 - Francis Colas

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