Python:扩展预定义命名元组

31

我有以下的命名元组:

from collections import namedtuple
ReadElement = namedtuple('ReadElement', 'address value')

然后我想要以下内容:

LookupElement = namedtuple('LookupElement', 'address value lookups')

两个namedtuple之间存在重复,我该如何子类化ReadElement以包含一个额外的字段?

class LookupElement(ReadElement):
    def __new__(self, address, value, lookups):
        self = super(LookupElement, self).__new__(address, value)
        l = list(self)
        l.append(lookups)
        return tuple(l)

不过,元组是在new语句中创建的,如果我将self修改为列表,则会失去类型信息,我该如何避免这种情况?

3个回答

41
你可以对由namedtuple创建的类进行子类化,但是你需要更仔细地研究生成的类。你需要添加另一个带有额外字段的__slots__属性,更新_fields属性,创建新的__repr___replace方法(它们硬编码了字段列表和类名称),并为附加字段添加额外的property对象。请参见文档中的示例
所有这些都是有点繁琐的工作。与其子类化,不如直接重用源类型的somenamedtuple._fields属性
LookupElement = namedtuple('LookupElement', ReadElement._fields + ('lookups',))
< p > namedtuple() 构造器的 field_names 参数不一定是字符串,也可以是一个字符串序列。只需取出 _fields 并通过连接新元组来添加更多元素即可。 < /p > < p > 演示: < /p >
>>> from collections import namedtuple
>>> ReadElement = namedtuple('ReadElement', 'address value')
>>> LookupElement = namedtuple('LookupElement', ReadElement._fields + ('lookups',))
>>> LookupElement._fields
('address', 'value', 'lookups')
>>> LookupElement('addr', 'val', 'lookup') 
LookupElement(address='addr', value='val', lookups='lookup')

这意味着扩展类型不是基本类型的子类。如果您必须有一个类层次结构,那么与其尝试使命名元组适合该模型,不如改用数据类。在大多数使用命名元组的情况下,数据类可以用于相同的目的,但可以轻松地进行子类化。


这是文档建议的做法,但如果您有一个带有生成字段的自定义命名元组呢? - Ethereal
@Ethereal:所有的namedtuple类都是自定义的。'_fields'属性仍将反映在类上的实际字段。 - Martijn Pieters
我发现这不是真的,_fields 不包含生成的字段!请参见文档中的 hypot 示例。 - Ethereal
1
@Ethereal,你在谈论一个“property”对象。我从来不会称之为生成的字段。不,namedtuple对象的子类上的附加属性不会反映在_fields属性中。如果您需要共享其他方法或属性,请考虑将这些放在混合类上,并在多个namedtuple类之间重用它们。 - Martijn Pieters
@MartijnPieters 没错,对于术语混淆和建议抱歉。 - Ethereal

7

扩展Martijn Pieters的答案:有一种方法可以使新的namedtuple类成为另一个类的子类,而无需进行hack。只需单独创建新的namedtuple,然后使用其__new__方法代替使用super

from collections import namedtuple

class ReadElement(namedtuple('ReadElement', ('address', 'value'))):
    def compute(self):
        return self.value + 1

_LookupElement = namedtuple('_LookupElement', ReadElement._fields + ('lookups',))

class LookupElement(_LookupElement, ReadElement):
    def __new__(self, address, value, lookups):
        return _LookupElement.__new__(LookupElement, address, value, lookups)

assert issubclass(LookupElement, ReadElement)
l = LookupElement('ad', 1, dict())
assert isinstance(l, ReadElement)
assert l.compute() == 2

似乎甚至不需要覆盖__new__就可以工作!

from collections import namedtuple

class ReadElement(namedtuple('ReadElement', ('address', 'value'))):
    def compute(self):
        return self.value + 1

class LookupElement(namedtuple('LookupElement', ReadElement._fields + ('lookups',)),
                    ReadElement):
    """nothing special to do"""
    pass

4

很容易构建一个允许你从其他namedtuples组合命名元组的东西,并引入新字段。

def extended_namedtuple(name, source_fields):
    assert isinstance(source_fields, list)
    new_type_fields = []
    for f in source_fields:
        try:
            new_type_fields.extend(f._fields)
        except:
            new_type_fields.append(f) 
    return namedtuple(name, new_type_fields) 

# source types
Name = namedtuple('Name', ['first_name', 'last_name'])
Address = namedtuple('Address', ['address_line1', 'city'])
# new type uses source types and adds additional ID field
Customer = extended_namedtuple('Customer', ['ID', Name, Address])
# using the new type
cust1 = Customer(1, 'Banana', 'Man', '29 Acacia Road', 'Nuttytown')
print(cust1)

这将输出以下内容:
Customer(ID=1, first_name='Banana', last_name='Man', address_line1='29 Acacia Road', city='Nuttytown')

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