Pergunta

I am working with a list of namedtuples. I would like to add a field to each named tuple after it has already been created. It seems I can do that by just referencing it as an attribute (as in namedtuple.attribute = 'foo'), but then it isn't added to the list of fields. Is there any reason why I shouldn't do it this way if I don't do anything with the fields list? Is there a better way to add a field?

>>> 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')
Foi útil?

Solução

What you do works because namedtuple(...) returns a new class. To actually get a Result object, you instantiate that class. So the correct way is:

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

And you'll find that adding attributes to these objects does not work. So the reason you shouldn't do it is because it doesn't work. Only abusing the class objects works, and I hope I don't need to go into detail why this is a horrible, horrible idea.

Note that regardless of whether you can add attributes to namedtuples or not (and even if you list all attributes you need beforehand), you cannot change a namedtuple object after it's created. Tuples are immutable. So if you need to change objects after creation for any reason, in any way or shape, you can't use namedtuple. You're better off defining a custom class (some of the stuff namedtuple adds for you doesn't even make sense for mutable objects).

Outras dicas

Notice that here you're modifying the type of the named tuples, not instances of that type. In this case, you'd probably want to create a new type with an additional field from the old one:

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

e.g.:

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

You can easily concatenate namedtuples, keeping in mind that they are immutable

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)

You cannot add a new field to a namedtuple after defining it. Only way is to create a new template and creating new namedtuple instances.

Analysis

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

result is not a tuple, but the class which creates tuples.

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

You create a new tuple like this:

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

>>> p.x
1

Prints the value x in p.

>>> p.x = 5

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

This throws error because tuple is immutable.

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

This doesn't change anything.

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

This doesn't change anything either.

Solution

>>> 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)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top