【问题标题】:How to use namedtuples in multiple inheritance如何在多重继承中使用命名元组
【发布时间】:2015-03-09 11:38:46
【问题描述】:

是否可以创建一个继承自多个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 或类似的东西实现多重继承?有人已经在某个地方实现了吗?

【问题讨论】:

  • 如果超类包含不同数量的字段或不同的字段名称,您将如何解决歧义?
  • 我不明白这个问题。我希望一切的行为与常规可变类的行为相同,除了它是不可变的。子类将具有所有基类的所有字段。因此,上面的示例相当于namedtuple(Test, ('foo', 'bar'))
  • 问题是因为你没有定义Test.__init__,所以只调用了first基类的__init__,而那个函数只需要一个(附加) 论点。

标签: python inheritance multiple-inheritance namedtuple


【解决方案1】:

您可以使用装饰器或元类将父命名元组字段组合成一个新的命名元组并将其添加到类__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

【讨论】:

    【解决方案2】:

    这段代码采用了与 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】:

      好吧,如果您只想要一个包含两个字段的命名元组,那么重新创建它很容易:

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

      【讨论】:

      • 这适用于我给出的示例,但实际情况稍微复杂一些。在我的真实用例中,我有一个直接或间接继承自namedtuple 的类。显然我的例子过于简单了。
      • 好的,你能重写一个不那么简单的例子吗?因为我不知道在这种情况下组合是否不是更好的方法。
      猜你喜欢
      • 2016-07-09
      • 1970-01-01
      • 2017-07-12
      • 2019-07-28
      • 2020-03-20
      • 1970-01-01
      • 2020-03-20
      • 2013-04-11
      • 1970-01-01
      相关资源
      最近更新 更多