How do you update a field on a collections.namedtuple instance?
answer
- You never assign to a field
- Build a modified copy instead
- One keyword-argument helper returns a new instance
- Underscored names avoid colliding with fields
- Copies are shallow, not deep
basics
~10 sYou do not update it in place — the record is immutable and attribute assignment raises AttributeError. Call _replace(field=value), which returns a new instance with that field changed and the rest copied across.
solid answer
~50 sRecords are immutable, so the update idiom is to build a modified copy: `_replace()` takes the changed fields as keyword arguments, applies them over the current values and returns a new instance of the same class. It is shallow — nested mutable objects are shared with the original, not copied — and a misspelled field name raises `TypeError` naming the unexpected argument, which is a useful guard the equivalent dict-based code lacks. The helpers all carry a leading underscore — `_replace()`, `_asdict()`, `_make()`, `_fields`, `_field_defaults` — because field names themselves are forbidden from starting with an underscore, so the API can never collide with your data. `_asdict()` returns a plain `dict` you can mutate or serialise, and `_make()` builds an instance from any iterable, which is the natural way to turn an incoming row into a record.
code
python · 8 linesfrom collections import namedtuple
Delivery = namedtuple("Delivery", "url attempts", defaults=(0,))
d = Delivery("https://example.test/hook")
retried = d._replace(attempts=d.attempts + 1)
print(d, retried)
print(retried._asdict())
print(Delivery._make(["https://example.test/other", 2]))go deeper
Know that a record cannot be changed in place and that the update idiom returns a new instance. Be able to name the helper that produces the modified copy and read its result.
Explain why the helpers are underscored, that the copy is shallow, and that an unknown field name raises rather than being accepted — and show _make() and _asdict() on either side of the boundary.
Discuss the consequences in running code: copy-per-update in hot paths, shared nested mutables defeating the immutability you advertised, and when that pushes a value type to a mutable object instead.
Frame the convention: which values in a system are immutable by contract, how updates flow as new values rather than mutations, and what that buys in shared caches and concurrent code.
**There is no in-place update.** A tuple-backed record stores its values in the tuple's own storage, and the generated field accessors are read-only descriptors; the class also declares empty `__slots__`, so there is no instance dictionary to stash a shadowing attribute in. `d.attempts = 1` raises `AttributeError: can't set attribute`. That is the whole point of the type: once built, a record is a value, safe to share between threads, safe to use as a dict key, and safe to hand to a caller without defensive copying. **The copy-with-changes idiom.** `_replace()` is the supported way to move to a new value: ```python retried = delivery._replace(attempts=delivery.attempts + 1) ``` It maps the current record to a dict of field-to-value, overlays the keyword arguments you passed, and builds a fresh instance of the same class from the result. The original is untouched. Three properties are worth stating explicitly in an interview. It is **shallow**: if a field holds a list, the new record shares that same list object with the old one, so mutating it changes what both records see — use `copy.deepcopy` first if that matters. It is **checked**: passing a name that is not a field raises `TypeError: Got unexpected field names: [...]`, so a typo fails loudly where a dict update would silently add a key. And it **returns the same class**, so a subclass of a record stays that subclass. **Why every helper is underscored.** This looks like a private API and is deliberately not one. A record's field names come from the caller — sometimes from a file header or a database cursor — so any public method name the class defined could be shadowed by a legitimate field. The design resolves the clash by reserving the underscore namespace: field names may not begin with an underscore (`namedtuple("Bad", ["_x"])` raises `ValueError: Field names cannot start with an underscore`), and every helper the class provides lives there. `_fields`, `_field_defaults`, `_make()`, `_asdict()` and `_replace()` are public API despite their spelling; a candidate who calls them private has read the convention without reading the reason. **The rest of the small API.** - `_fields` is a tuple of the field names in order. It is what you iterate to build a header row, and `record._fields` combined with `zip` gives you name/value pairs without a dict. - `_field_defaults` maps the defaulted field names to their default values; empty when none were declared. - `_make(iterable)` is a classmethod that builds an instance from any iterable of the right length. It is the fast path for turning a parsed row or a database tuple into a record, and it is what `_replace()` uses internally. - `_asdict()` returns a plain `dict` of field name to value. Use it when something downstream needs a mapping — most importantly serialisation, since encoding the record directly writes a JSON array rather than an object. **Patterns this enables.** Because updates are copies, a pipeline stage reads naturally as a chain of transformations: each stage takes a record and returns a new one, and nothing upstream can observe a half-applied change. Where a record is a cache key or lives in a set, immutability is what makes that legal at all — a mutable record whose fields changed after insertion would sit in the wrong hash bucket forever. And where the same value must be updated frequently in a hot loop, the copying is real work: allocating a new tuple per change is cheap but not free, and if you are doing it thousands of times per item, that is a signal the value should be a mutable object rather than a record. **Common mistakes.** Writing `record._replace(attempts=1)` and throwing the result away, expecting mutation — `_replace()` returns; it does not modify. Reaching for `_asdict()`, mutating the dict and rebuilding with `**` when `_replace()` says the same thing more clearly and validates the names. Calling the underscored helpers 'private' and hand-rolling replacements for them. And assuming the copy is deep: it is not, and shared nested state is the classic way an 'immutable' record turns out to have been mutable all along. **When copying stops being pleasant.** If most fields change on most updates, or updates happen in a tight loop, the copy-with-changes idiom is fighting you. That is the signal to move from a tuple-backed record to a mutable object, and it is a design decision rather than a syntax one.
- Why do the helper names on a namedtuple class start with an underscore?To keep the class's own API out of the caller's namespace. Field names are supplied by the caller and could otherwise shadow a method, so the design forbids field names that begin with an underscore — the factory raises `ValueError` for one — and puts every helper there instead. `_fields`, `_asdict()` and `_replace()` are public API despite the spelling.
- Is the copy produced by _replace shallow or deep?Shallow. Only the fields you name are substituted; every other field is carried over as the same object, so a list or dict inside the record is shared with the original and mutating it is visible through both. Use `copy.deepcopy` explicitly when independence matters — or, better, keep only immutable values in a record so the question does not arise.
- What happens if you misspell a field name in a _replace call?It raises `TypeError` and names the unexpected fields, so the typo fails at the call rather than silently creating a nonsense record. That is a small but real advantage over the `_asdict()`-mutate-rebuild pattern, where an unknown key would be rejected only at reconstruction, and over dict-based records, where it would just be added.
It is a printed form rather than a whiteboard: you never rub out one line, you photocopy the form with the one box filled in differently.
saying these in an interview costs you the question
- Thinks _replace mutates the record in place
- Calls _fields and _asdict private, off-limits API
- Assumes the copy is deep and nested state is independent
- Says a misspelled field name is silently added
- Suggests object.__setattr__ to force a field change