Python: 扩展一个预定义的命名元组

Python: Extending a predefined named tuple

我有以下命名元组:

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

然后我想要以下内容:

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

两个命名元组之间存在重复,我如何子类化 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 修改为列表,我将丢失类型信息,如何避免这种情况?

您可以子class一个namedtuple生成的class,但您需要更仔细地研究生成的class。您需要添加另一个带有额外字段的 __slots__ 属性,更新 _fields 属性,创建新的 __repr___replace 方法(它们硬编码字段列表和 class 名称)并为附加字段添加额外的 property 对象。见 example in the documentation.

这些工作有点太多了。而不是 subclass,我只是重用源类型的 somenamedtuple._fields attribute

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

namedtuple() 构造函数的 field_names 参数不一定是字符串,也可以是字符串序列。只需采用 _fields 并通过连接一个新元组来添加更多元素。

演示:

>>> 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')

这意味着扩展类型不是基本类型的子class。如果您必须有一个 class 层次结构,那么我不会尝试使命名元组适合该模型,而是改用 。 Dataclasses 在大多数使用命名元组的用例中可以达到相同的目的,但可以很容易地被 subclassed.

很容易将一些东西组合在一起,使您可以从其他命名元组组成命名元组并引入新字段。

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')

扩展一种使新命名元组class成为另一个的子class的方法,而不必破解。只需单独创建新的 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