Как добавить поля в namedtuple?

Я работаю со списком namedtuples. Я хотел бы добавить поле к каждому названному кортежу после его создания. Кажется, я могу сделать это, просто ссылаясь на него как на атрибут (как в namedtuple.attribute = 'foo'), но затем он не добавляется в список полей. Есть ли причина, почему я не должен делать это таким образом, если я ничего не делаю с списком полей? Есть ли лучший способ добавить поле?

>>> from collections import namedtuple
>>> result = namedtuple('Result',['x','y'])
>>> result.x = 5
>>> result.y = 6
>>> (result.x, result.y)
(5, 6)
>>> result.description = 'point'
>>> (result.x, result.y, result.description)
(5, 6, 'point')
>>> result._fields
('x', 'y')

Ответ 1

Что вы делаете, потому что namedtuple(...) возвращает новый класс. Чтобы получить объект Result, вы создаете экземпляр этого класса. Итак, правильный способ:

Result = namedtuple('Result', ['x', 'y'])
result = Result(5, 6)

И вы обнаружите, что добавление атрибутов к этим объектам не работает. Поэтому причина, по которой вы не должны этого делать, заключается в том, что она не работает. Только злоупотребление объектами класса работает, и я надеюсь, что мне не нужно подробно излагать, почему это ужасная, ужасная идея.

Обратите внимание, что независимо от того, можно ли добавлять атрибуты в namedtuples или нет (и даже если вы перечисляете все необходимые вам атрибуты), вы не сможете изменить объект namedtuple после его создания. Кортежи неизменяемы. Поэтому, если вам нужно изменить объекты после создания по какой-либо причине, любым способом или формой, вы не можете использовать namedtuple. Вам лучше определить пользовательский класс (некоторые из добавленных вещей namedtuple для вас даже не имеют смысла для изменяемых объектов).

Ответ 2

Обратите внимание, что здесь вы изменяете type названных кортежей, а не экземпляры этого типа. В этом случае вы, вероятно, захотите создать новый тип с дополнительным полем из старого:

result = namedtuple('Result',result._fields+('point',))

например:.

>>> result = namedtuple('Result',['x','y'])
>>> result = namedtuple('Result',result._fields+('point',))
>>> result._fields
('x', 'y', 'point')

Ответ 3

Вы не можете добавить новое поле в namedtuple после его определения. Единственный способ - создать новый шаблон и создать новые экземпляры namedtuple.

Анализ

>>> from collections import namedtuple
>>> result = namedtuple('Result',['x','y'])
>>> result
<class '__main__.Result'>

result не является кортежем, а классом, который создает кортежи.

>>> result.x
<property object at 0x02B942A0>

Вы создаете новый кортеж следующим образом:

>>> p = result(1, 2)
>>> p
Result(x=1, y=2)

>>> p.x
1

Печатает значение x в p.

>>> p.x = 5

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    p.x = 5
AttributeError: can't set attribute

Это вызывает ошибку, поскольку кортеж является неизменным.

>>> result.x = 5
>>> result
<class '__main__.Result'>
>>> result._fields
('x', 'y')
>>> p = result(1, 2)
>>> p
Result(x=1, y=2)

Это ничего не меняет.

>>> result.description = 'point'
>>> result
<class '__main__.Result'>
>>> result._fields
('x', 'y')

Это тоже ничего не меняет.

Решение

>>> result = namedtuple('Result', ['x','y'])
>>> p = result(1, 2)
>>> p
Result(x=1, y=2)
>>> # I need one more field
>>> result = namedtuple('Result',['x','y','z'])
>>> p1 = result(1, 2, 3)
>>> p1
Result(x=1, y=2, z=3)
>>> p
Result(x=1, y=2)

Ответ 4

Вы можете легко конкатенировать namedtuples, имея в виду, что они неизменяемы

from collections import namedtuple

T1 = namedtuple('T1', 'a,b')
T2 = namedtuple('T2', 'c,d')

t1 = T1(1,2)
t2 = T2(3,4)

def sum_nt_classes(*args):
    return namedtuple('_', ' '.join(sum(map(lambda t:t._fields, args), ())))

def sum_nt_instances(*args):
    return sum_nt_classes(*args)(*sum(args,()))

print sum_nt_classes(T1,T2)(5,6,7,8)
print sum_nt_instances(t1,t2)