Python3 — replacing a dynamically chosen namedtuple field

2.3k views Asked by At

I am modeling database records using collections.namedtuple. At times, I want the user to be able to replace the contents of an arbitrary field. The _replace() method permits replacing the contents of a specific field as long as we can specify its name as part of the argument: somenamedtuple._replace(somefield=newcontent). But if the name itself is to be dynamically supplied by the user, I am unable to find a way to do it.

Here is a minimal working example:

from collections import namedtuple

fields = ['one', 'two', 'three']
Record = namedtuple('Record', fields)
# Populate fields.
record = Record(*tuple(['empty' for i in fields]))
while True:
    # Show what we have already.
    print('0: quit')
    for i in range(len(fields)):
    print('{}: {}: {}'.format(i+1, fields[i], record[i]))
    to_change = int(input('Field to change: '))
    if not to_change:
        break
    else:
        new_content = input('New content: ')
        field_to_change = {fields[to_change-1]:new_content}
        print('Setting', field_to_change)
        record._replace(**field_to_change)
print('Finished.')
print(record)

Output (Ipython 1.0.0, Python 3.3.1) follows.

In [1]: run test_setattr_namedtuple
0: quit
1: one: empty
2: two: empty
3: three: empty
Field to set: 2
New content: asdf
Setting {'two': 'asdf'}
0: quit
1: one: empty
2: two: empty
3: three: empty
Field to set: 0
Finished.
Record(one='empty', two='empty', three='empty')

In [2]: 

The record._replace() line is attempting to set 'two' to 'asdf', rather than two and so fails silently. I had thought of using eval inside _replace(), but _replace() does not accept expressions as arguments.

I also tried the built-in function setattr, but it doesn't work with namedtuples, presumably because they are immutable.

1

There are 1 answers

0
Martijn Pieters On BEST ANSWER

The ._replace() method returns the altered named tuple instance. You are discarding the returned value.

Like tuples, namedtuple-derived class instances are immutable, and ._replace() does not change the value in-place.

Replace the original value with the new:

record = record._replace(**field_to_change)