Skip to content

← Memory, Copies and Slow Code step 7 of 38

Medium Primitives

Deep-Copy Only the Sections You Choose

A configuration has named sections, and each section is a list. The caller will change some sections of a clone, so those need their own copy. The rest stay shared with the original. Write clone_config:

def clone_config(cfg: Config, *, deep_keys: frozenset[str]) -> Config: ...

The supplied Config has one field, sections: dict[str, list[object]].

What to write

Return a new Config with a new sections dictionary, keys in the same order as the original. For each section:

  • If its key is in deep_keys, store copy.deepcopy(value, memo).
  • Otherwise, store the original list itself, without copying it.

Follow these rules:

  • Create one new memo dictionary for each call to clone_config, and pass that same memo to every deepcopy call. An object shared by two deep sections then stays shared in the clone.
  • Ignore names in deep_keys that are not in cfg.sections.
  • If deepcopy raises TypeError for a section, raise ValueError(f"section {key!r} is not deep-copyable: {err}") from that error, so the TypeError is its __cause__. Let other exceptions through unchanged.
  • Go through the sections in order, so that the error names the first section that fails.

The rule is per key. Suppose two keys hold the same list, and only one of them is in deep_keys. The clone then holds a copy under that key and the original list under the other.

What the driver does

Keep solve and _visible as they are. solve builds fresh lists from the input numbers. If cyclic_key is not empty, it appends that section's list to itself. If module_key is not empty, it appends the math module to that section. deepcopy cannot copy a module, so that section fails only if its key is in deep_keys. Every key named by cyclic_key, module_key or appends exists in sections.

It then clones the configuration and appends each number in appends to the clone's section. A shared section is the original's list, so that append shows up in the original too.

The report contains:

Key Meaning
error Empty on success, otherwise the ValueError text up to the first colon
original The original sections, keeping only their integers
clone The clone's sections, keeping only their integers, or empty on error
shared The sorted keys whose list is the same object in the clone and the original, or empty on error
cycle_local Whether the cyclic_key section in the clone contains itself, or False on error or with no cyclic key

A shared cyclic section also gives cycle_local=True, because the original list contains itself. Read cycle_local together with shared.

The report shows only integers, so it cannot show which objects two deep sections share. Test clone_config directly for that. Your code must pass mypy --strict.