# Python rules

Follow these CodeWiki-derived rules when you work in this project.

- Reading `kwargs.get("timeout", 5)` while ignoring leftover keys turns `timeuot=2` into a silent request for the default timeout.
  Source: [*args and **kwargs](https://codewiki.com/python/args-kwargs/)
- Do not assume this is safe: a wrapper that calls `target(timeout=wrapper_timeout, **kwargs)` fails when the caller's mapping already contains `timeout`; Python does not use “last value wins” for call arguments.
  Source: [*args and **kwargs](https://codewiki.com/python/args-kwargs/)
- `args` is a new tuple and `kwargs` is a new dictionary, but a list or dictionary stored inside either one is still the caller's object.
  Source: [*args and **kwargs](https://codewiki.com/python/args-kwargs/)
- `send(*recipient)` supplies one positional argument per character when `recipient` is a string, and unpacking a generator consumes it before the function body begins.
  Source: [*args and **kwargs](https://codewiki.com/python/args-kwargs/)
- A runtime wrapper written as `def wrapper(*args, **kwargs)` can erase useful static information even when `functools.wraps()` repairs runtime metadata.
  Source: [*args and **kwargs](https://codewiki.com/python/args-kwargs/)
- Do not assume this is safe: passing an entire options dictionary through several layers can leak credentials into logs or deliver a control such as `verify=False` to a lower-level API that the outer interface never intended to expose.
  Source: [*args and **kwargs](https://codewiki.com/python/args-kwargs/)
- Do not assume this is safe: calling a coroutine function without `await` only creates a coroutine object.
  Why: Its body does not run, and collection of that object may produce `RuntimeWarning: coroutine was never awaited`. Fix: `await` it in the current coroutine. For concurrent work, schedule it with `TaskGroup.create_task()` or `asyncio.create_task()`, then retain and await the task.
  Source: [asyncio](https://codewiki.com/python/asyncio/)
- `time.sleep()`, a synchronous HTTP client, and ordinary file reads still block the event loop when called inside `async def`.
  Why: Other tasks cannot run during that block even when they are ready. Fix: prefer a genuinely asynchronous library. Send short, unavoidable blocking I/O through `asyncio.to_thread()`, and send CPU-heavy work to processes or a dedicated compute environment.
  Source: [asyncio](https://codewiki.com/python/asyncio/)
- Do not treat `asyncio.create_task(do_work())` as unmanaged background work loses ownership.
  Why: The loop keeps only weak references to tasks, so an unreferenced task may be collected before completion, and nobody may retrieve its exception. Fix: prefer `TaskGroup`. For genuinely long-lived background tasks, keep strong references in a set, remove them on completion, and define shutdown.
  Source: [asyncio](https://codewiki.com/python/asyncio/)
- Catching `CancelledError` and returning breaks the cancellation protocol used by `TaskGroup` and `asyncio.timeout()`.
  Why: The task may retain resources, and the caller cannot tell whether work stopped. Fix: release resources in `finally` and normally let `CancelledError` propagate. Only code that deliberately suppresses cancellation should also clear the task's cancellation state.
  Source: [asyncio](https://codewiki.com/python/asyncio/)
- Calling `gather(*(fetch(x) for x in items))` over an arbitrary input creates all the work at once.
  Why: That can exhaust a connection pool, file descriptors, or downstream service capacity. Fix: bound in-flight calls with a semaphore, or connect production to consumption with a bounded queue. Derive the limit from resource budgets and service constraints, not a guess.
  Source: [asyncio](https://codewiki.com/python/asyncio/)
- Calling `asyncio.run()` inside a notebook, test runner, or web framework that already owns a running event loop raises `RuntimeError` and splits the host's lifecycle.
  Why: Fix: call `asyncio.run()` only at the top of a synchronous program. Inside an async entry point, `await` directly and leave ownership of the event loop with the host framework.
  Source: [asyncio](https://codewiki.com/python/asyncio/)
- Do not assume this is safe: "A closure saves the variable's value at that moment" predicts the wrong result for rebinding and loop-created callbacks.
  Why: A closure normally retains access to a binding rather than freezing an object when the function is created.
  Source: [Closures](https://codewiki.com/python/closures/)
- Assignment to a name inside the inner function makes that name local throughout the function's code block by default.
  Why: Even an assignment in a branch that never runs affects compile-time classification.
  Source: [Closures](https://codewiki.com/python/closures/)
- Calling a stateful factory once outside a loop and registering the same result for several consumers makes them share cells unexpectedly.
  Why: Calling the factory again for every event has the opposite bug: persistent state keeps resetting.
  Source: [Closures](https://codewiki.com/python/closures/)
- Late binding isn't limited to `lambda`.
  Why: A nested `def`, callback registration, task completion handler, or comprehension can make several functions share the final iteration binding.
  Source: [Closures](https://codewiki.com/python/closures/)
- A long-lived registered closure keeps the bindings it uses reachable.
  Why: If a binding points to a request context, cache, service container, or large dataset, those objects may outlive the actual work.
  Source: [Closures](https://codewiki.com/python/closures/)
- `function.__closure__[0]` has no stable business meaning.
  Why: Adding another free variable can change the name-to-cell positions, and a function with no free variables has `None` instead of a tuple.
  Source: [Closures](https://codewiki.com/python/closures/)
- Python doesn't call an object's `__exit__()` when its `__enter__()` raises after partially acquiring resources.
  Why: Leaving all rollback work to the exit method leaks anything already acquired during entry.
  Source: [Context managers](https://codewiki.com/python/context-managers/)
- In `with manager as value`, `value` is the result of `manager.__enter__()`.
  Why: Generated code often assumes it must be `manager`, calls the wrong interface, or loses track of the true resource owner.
  Source: [Context managers](https://codewiki.com/python/context-managers/)
- Any truthy return from `__exit__()` suppresses an exception from the block.
  Why: Returning an exception object, status dictionary, or `self` can turn failure into normal control flow without intending to.
  Source: [Context managers](https://codewiki.com/python/context-managers/)
- A generator wrapped by `@contextmanager` receives a block exception at the `yield` expression.
  Why: Cleanup written only on the following line can be skipped, and catching the exception without re-raising it suppresses the original failure.
  Source: [Context managers](https://codewiki.com/python/context-managers/)
- `resources = [open(path) for path in paths]` completes the entire list before anything is registered with an `ExitStack`.
  Why: If a middle `open()` fails, the expression never returns and the earlier files have no owner.
  Source: [Context managers](https://codewiki.com/python/context-managers/)
- A generator context-manager instance can normally be entered only once, and a file is closed after its first exit.
  Why: Putting an async resource in ordinary `with`, or a synchronous resource in `async with`, also selects the wrong protocol.
  Source: [Context managers](https://codewiki.com/python/context-managers/)
- `if status == "ready" or "queued"` always passes because the nonempty string `"queued"` is truthy by itself.
  Source: [Control flow](https://codewiki.com/python/control-flow/)
- Do not assume this is safe: removing elements from `items` inside `for item in items` shifts later positions, so the iterator can skip adjacent items.
  Source: [Control flow](https://codewiki.com/python/control-flow/)
- When a state update sits at the bottom of the body, an earlier `continue` can bypass it and cause an infinite loop.
  Source: [Control flow](https://codewiki.com/python/control-flow/)
- Do not assume this is safe: loop `else` depends on whether `break` ran, not on the result of the final `if` inside the loop body.
  Source: [Control flow](https://codewiki.com/python/control-flow/)
- Do not assume this is safe: a bare name in `case RED:` is normally a capture pattern: it matches any subject and binds that subject to `RED` instead of comparing with an existing variable.
  Source: [Control flow](https://codewiki.com/python/control-flow/)
- Do not assume this is safe: without `@wraps(func)`, callers see a broadly shaped function named `wrapper`, and the original documentation, annotations, and `__wrapped__` chain disappear.
  Why: Routers, dependency injection, tests, or documentation tools that inspect signatures may read the wrong interface.
  Source: [Decorators](https://codewiki.com/python/decorators/)
- Do not assume this is safe: a wrapper that calls `func(*args, **kwargs)` without `return` silently changes every successful result to `None`.
  Why: Catching `Exception` and returning a fallback also rewrites the original contract and can disguise a programming error as a normal result.
  Source: [Decorators](https://codewiki.com/python/decorators/)
- Registration, opening a connection, or reading mutable configuration inside a decorator factory or `decorate()` normally happens during import.
  Why: Test discovery, automatic reloaders, and multiprocess startup can repeat those effects, while a failure prevents the module from loading.
  Source: [Decorators](https://codewiki.com/python/decorators/)
- The arrangement of `@cache`, `@authorize`, `@retry`, and `@transaction` isn't cosmetic.
  Why: A cache outside authorization may reuse a result across permission contexts, while retry inside or outside a transaction changes whether each attempt gets a fresh transaction.
  Source: [Decorators](https://codewiki.com/python/decorators/)
- A sync wrapper that returns a coroutine, or an async wrapper that awaits a synchronous result, breaks the call contract.
  Why: Checking whether a call result is awaitable is also too late when types and framework detection were already wrong before the call.
  Source: [Decorators](https://codewiki.com/python/decorators/)
- Reusing one stateful decorator instance for several functions can make them share counters, caches, or rate windows.
  Why: Even if each function gets its own wrapper, each wrapper closure may still capture the same decorator object.
  Source: [Decorators](https://codewiki.com/python/decorators/)
- A list, dictionary, or set written directly as a default is reused by every call that omits that argument.
  Why: It is shared state across calls, not a fresh container per call.
  Source: [Functions](https://codewiki.com/python/functions/)
- Returning different shapes from different branches pushes complexity onto every caller.
  Why: For example, one branch may return a pair while another returns an empty string; an annotation cannot make that contract coherent for you.
  Source: [Functions](https://codewiki.com/python/functions/)
- Do not assume this is safe: “Passing an object reference” does not mean a function cannot affect its caller.
  Why: Assigning a new value to a parameter only changes a local binding, but `items.append(...)` mutates the list that both sides reference.
  Source: [Functions](https://codewiki.com/python/functions/)
- Do not assume this is safe: type annotations do not automatically convert a string to a number or reject a value of the wrong type.
  Why: Generated code often adds complete annotations and then assumes the runtime boundary has been validated.
  Source: [Functions](https://codewiki.com/python/functions/)
- Giving every wrapper `*args, **kwargs` to “stay flexible” hides the real interface and lets misspelled keywords travel deeper before failing.
  Why: Forwarding code may also drop the return value or swallow a useful `TypeError`.
  Source: [Functions](https://codewiki.com/python/functions/)
- `partial(send, timeout=5)` doesn't lock `timeout`.
  Why: If a caller runs `configured(timeout=30)`, the call-time keyword overrides the stored value.
  Source: [functools](https://codewiki.com/python/functools/)
- A plain partial object doesn't bind an instance through the descriptor protocol like a function in a class body.
  Why: Its stored positional argument may occupy the slot intended for `self`, and the bug often appears only on the first instance call.
  Source: [functools](https://codewiki.com/python/functools/)
- `wraps()` copies metadata and establishes the `__wrapped__` chain, but it doesn't forward positional-only parameters, keyword-only parameters, return values, exceptions, or asynchronous execution for you.
  Why: A wrapper with a correct name and signature can still swallow a result or finish timing too early.
  Source: [functools](https://codewiki.com/python/functools/)
- Do not assume this is safe: without `initial`, an empty iterable raises `TypeError`.
  Why: Generated code often calls `reduce()` after filtering but tests only non-empty examples, so the first fully filtered production input triggers the failure.
  Source: [functools](https://codewiki.com/python/functools/)
- Do not assume this is safe: `return left < right` produces only `False` or `True`, numerically `0` or `1`, and never the negative result required for “less than.” Once wrapped by `cmp_to_key()`, equality and ordering blur together, and the result can look sorted without satisfying the contract.
  Source: [functools](https://codewiki.com/python/functools/)
- `total_ordering` fills in syntax from existing methods; it doesn't check that `__eq__()` and `__lt__()` are consistent.
  Why: If equality uses only an identifier while ordering also uses time, two objects can be both equal and ordered.
  Source: [functools](https://codewiki.com/python/functools/)
- The first `list(iterator)`, `sum(iterator)`, or loop advances to the end.
  Why: An empty result on reuse means the same cursor is exhausted; the data hasn't mysteriously disappeared.
  Source: [Generators and iterators](https://codewiki.com/python/generators-iterators/)
- `with open(path) as file: return (parse(line) for line in file)` hasn't read the file when it returns.
  Why: The `with` block has closed it by the time the caller iterates, producing `ValueError: I/O operation on closed file`.
  Source: [Generators and iterators](https://codewiki.com/python/generators-iterators/)
- `print(list(rows))`, `next(rows)`, and `target in rows` all consume.
  Why: A successful membership test leaves only the tail after the matching item; looking for an absent value in an infinite iterator might never finish.
  Source: [Generators and iterators](https://codewiki.com/python/generators-iterators/)
- A custom iterator's `__next__()` uses `raise StopIteration` to report completion, but `StopIteration` escaping from a generator body becomes `RuntimeError: generator raised StopIteration`.
  Why: Copying protocol code directly changes the outcome.
  Source: [Generators and iterators](https://codewiki.com/python/generators-iterators/)
- A `break` in a `for` loop leaves the loop; it doesn't call `close()` on an arbitrary iterator.
  Why: If a variable still references the generator, its `finally` block and resources wait for resumption, closing, or collection.
  Source: [Generators and iterators](https://codewiki.com/python/generators-iterators/)
- `itertools.tee(source, 2)` returns two independent cursor views; it doesn't copy all source data up front.
  Why: Items read by the fast consumer but not yet seen by the slow one must stay in an internal buffer, which keeps growing if the gap grows.
  Source: [Generators and iterators](https://codewiki.com/python/generators-iterators/)
- `[send(message) for message in messages]` builds a list even when the caller cares only about `send()` side effects.
  Why: If the function returns `None`, it also leaves behind a useless list of `None` values. Fix: Use an ordinary `for` loop so the side effect and failure point are visible. Use a list comprehension only when the result list is required, and test exception propagation.
  Source: [List comprehensions](https://codewiki.com/python/list-comprehensions/)
- `[value if valid(value) for value in values]` is invalid syntax because a conditional expression in the result position is missing its `else`.
  Why: Models borrowing syntax from other languages often generate this wrong ordering. Fix: To discard invalid items, write `[value for value in values if valid(value)]`. To keep every item but select a result, write `[value if valid(value) else fallback for value in values]`.
  Source: [List comprehensions](https://codewiki.com/python/list-comprehensions/)
- Do not assume this is safe: `[parse(raw) for raw in rows if parse(raw) is not None]` calls `parse()` twice for every accepted item.
  Why: Besides the extra work, a function that reads a cache, counter, or clock may return different results between calls. Fix: Use a parenthesized `:=` to retain a simple, readable intermediate value, or separate parsing, validation, and append in an ordinary loop. Don't introduce a surprising binding just to save a line.
  Source: [List comprehensions](https://codewiki.com/python/list-comprehensions/)
- The order of multiple `for` clauses controls traversal order and name visibility.
  Why: Generated code may swap inner and outer clauses, causing different ordering, different combinations, or a reference before binding. Fix: Expand the comprehension into nested loops and write down the input cardinality and dependent names at each level. If more than two levels still need explanation, keep the expanded loop and give intermediates domain names.
  Source: [List comprehensions](https://codewiki.com/python/list-comprehensions/)
- `sum([price quantity for price, quantity in lines])` constructs the complete list before summing it.
  Why: For a large input, that intermediate list may have no other use. Fix: For one-time consumption, write `sum(price quantity for price, quantity in lines)`. Keep a list comprehension when you need indexing, repeated traversal, or mutation, and don't claim either form is faster without measuring real data on the target runtime.
  Source: [List comprehensions](https://codewiki.com/python/list-comprehensions/)
- `[lambda: item for item in items]` creates functions that read `item` later.
  Why: When they run after the comprehension, they normally all see the same final binding rather than a snapshot from each iteration. Fix: If each iteration needs its own value, use `lambda item=item: item` to bind a default at function creation, or call a named factory that accepts `item`. Prefer a factory when callbacks capture more state because it makes ownership visible.
  Source: [List comprehensions](https://codewiki.com/python/list-comprehensions/)
- Do not assume this is safe: when a function reads time, randomness, environment variables, a database, or mutable globals, equal arguments do not guarantee that the desired result stays equal.
  Why: The decorator cannot see those dependencies and keeps returning the old result.
  Source: [lru_cache function caching](https://codewiki.com/python/lru-cache/)
- `@cache` and `lru_cache(maxsize=None)` retain the arguments and result of every distinct call.
  Why: When user IDs, search text, or timestamps keep changing, process memory can grow with the key space.
  Source: [lru_cache function caching](https://codewiki.com/python/lru-cache/)
- A cache hit returns the same object reference.
  Why: A change that one caller makes to a list, dictionary, or custom object becomes the cached content observed by later callers.
  Source: [lru_cache function caching](https://codewiki.com/python/lru-cache/)
- The wrapper keeps its internal data structure coherent, but two threads may execute the original function concurrently for the same missing key.
  Why: If that function bills, writes, or sends a message, duplicate execution can violate business semantics.
  Source: [lru_cache function caching](https://codewiki.com/python/lru-cache/)
- When an instance method is decorated, `self` is part of the cache key and is retained by the cache.
  Why: Many short-lived instances can therefore survive until their entries are evicted or the cache is cleared.
  Source: [lru_cache function caching](https://codewiki.com/python/lru-cache/)
- Debugging code calls `list(result)`, then application code iterates the same `map` or `filter` object; the second consumer gets only what remains.
  Why: Checking that the object was constructed also doesn't trigger exceptions in the transformation function.
  Source: [map, filter and reduce](https://codewiki.com/python/map-filter-reduce/)
- `filter(None, values)` removes every false value, including legitimate `0`, `False`, `""`, and empty containers.
  Why: Generated cleaning code often collapses “missing” and “business value is zero” into one condition.
  Source: [map, filter and reduce](https://codewiki.com/python/map-filter-reduce/)
- By default, `map(function, left, right)` stops when the shorter input ends, with no notice about the longer input's tail.
  Why: When the columns must correspond row for row, this turns a missing row into silent data loss.
  Source: [map, filter and reduce](https://codewiki.com/python/map-filter-reduce/)
- Do not assume this is safe: `reduce()` without an initializer raises `TypeError` on empty input and uses the first item directly as the accumulator.
  Why: When the item type and result type differ, the reduction function may not expose the mismatch until the second item arrives.
  Source: [map, filter and reduce](https://codewiki.com/python/map-filter-reduce/)
- `reduce(lambda acc, item: acc.update(item), mappings, {})` returns `None` on its first step, so the next step can't continue.
  Why: The same problem appears with lambdas that return `list.append()`, because these in-place methods conventionally return `None`.
  Source: [map, filter and reduce](https://codewiki.com/python/map-filter-reduce/)
- Reimplementing `sum()`, `min()`, `max()`, `any()`, `all()`, or `str.join()` with `reduce()` forces readers to parse a custom binary function.
  Why: Repeatedly joining lists with `left + right` also hides the data movement inside one lambda.
  Source: [map, filter and reduce](https://codewiki.com/python/map-filter-reduce/)
- Do not treat assignment as copying quietly spreads shared mutable state.
  Why: `backup = settings` only adds another name for the same dictionary. Fix: Use `copy()` or the corresponding constructor when the outer container must be independent. If nested objects must also be independent, decide whether `copy.deepcopy()` matches the domain semantics. State what should remain shared before copying.
  Source: [Python fundamentals](https://codewiki.com/python/python-fundamentals/)
- Do not assume this is safe: comparing string or numeric values with `is` can depend on whether an implementation happened to reuse an object.
  Why: Equal values do not imply identical objects. Fix: Use `==` for value equality and `is` only for identity. Write `value is None` for the missing-value sentinel, and do not depend on the concrete number returned by `id()` or on small-integer caching.
  Source: [Python fundamentals](https://codewiki.com/python/python-fundamentals/)
- Using `value or default` for missing data also replaces `0`, `False`, empty strings, and empty containers.
  Why: If any of them is valid, the program loses information. Fix: Compare with `None` explicitly when it means missing. If a dictionary must distinguish an absent key from a false value, use `key in mapping` or a separate sentinel object.
  Source: [Python fundamentals](https://codewiki.com/python/python-fundamentals/)
- A list or dictionary used directly as a default parameter is shared by every call that omits that argument.
  Why: State can leak across requests or tests. Fix: Use `None` as the default and create a new container in the function body. If a shared cache is intentional, give it an explicit name, owner, and lifetime instead of hiding it in a default parameter.
  Source: [Python fundamentals](https://codewiki.com/python/python-fundamentals/)
- Returning an empty result from a broad `except Exception` swallows misspelled names, invalid attribute access, and contract failures together.
  Why: The caller sees “no data” instead of the defect. Fix: Narrow the `try` suite and catch specific exceptions that this boundary can recover from. Otherwise, add context and re-raise while preserving the exception chain. Put cleanup in a context manager or `finally`.
  Source: [Python fundamentals](https://codewiki.com/python/python-fundamentals/)
- A `count += 1` near the end of a function makes `count` local throughout that function.
  Why: An earlier `print(count)` therefore raises `UnboundLocalError` instead of reading the global binding.
  Source: [Scope and namespaces](https://codewiki.com/python/scope-namespaces/)
- Adding `global` as a quick response to `UnboundLocalError` can turn request, user, or object state into module-wide state.
  Why: A one-call test passes, while repeated or concurrent calls interfere with one another.
  Source: [Scope and namespaces](https://codewiki.com/python/scope-namespaces/)
- Bindings such as `list = records`, `id = order.id`, and `sum = 0` shadow their built-in counterparts.
  Why: A later `list(...)`, `id(...)`, or `sum(...)` may fail far from the binding that caused the problem.
  Source: [Scope and namespaces](https://codewiki.com/python/scope-namespaces/)
- A class-body binding such as `rate = 0.2` doesn't make bare `rate` in a method resolve to the class attribute.
  Why: Ordinary name lookup in the method skips the class namespace and continues through the module and built-ins.
  Source: [Scope and namespaces](https://codewiki.com/python/scope-namespaces/)
- Generated code sometimes writes `locals()[field] = value` to create local variables dynamically.
  Why: In an optimized scope on Python 3.14, this only changes the returned dictionary; it doesn't establish a local binding readable by a bare name.
  Source: [Scope and namespaces](https://codewiki.com/python/scope-namespaces/)
- An object isn't necessarily destroyed when a name leaves scope; a container, closure, or external component may still hold a reference.
  Why: Conversely, rebinding a name doesn't mutate the old object.
  Source: [Scope and namespaces](https://codewiki.com/python/scope-namespaces/)
- `{}` always creates a dictionary.
  Why: Code may survive initialization and fail only when it first calls a set method.
  Source: [Sets](https://codewiki.com/python/sets/)
- Do not assume this is safe: “Only immutable objects can enter a set” is a rough mnemonic, not Python's rule.
  Why: A tuple containing a list is still unhashable, while a user-defined class instance may be hashable by default even when its attributes can change.
  Source: [Sets](https://codewiki.com/python/sets/)
- Small sets often display in an apparently stable order, so generated code writes `list(a_set)` directly to JSON, snapshots, or test expectations.
  Why: The language contract does not promise that order.
  Source: [Sets](https://codewiki.com/python/sets/)
- `current - desired` and `desired - current` answer opposite questions, while `current -= desired` also changes the original set that a caller may share.
  Why: Short variable names hide both mistakes.
  Source: [Sets](https://codewiki.com/python/sets/)
- Calling `add()`, `remove()`, or `discard()` on `members` inside `for member in members` changes the same set's size and raises `RuntimeError`.
  Why: Mechanically replacing `remove()` with `discard()` does not repair invalidated iteration.
  Source: [Sets](https://codewiki.com/python/sets/)
- `set(records)` does more than delete duplicate rows.
  Why: It also discards occurrence counts and can merge equal values such as `True`, `1`, and `1.0` into one member.
  Source: [Sets](https://codewiki.com/python/sets/)
- An AI suggestion or quick refactor may rename `working = template` as though it created a working copy.
  Why: Both names still designate the same object, so an in-place update changes the template. Fix: state whether shared identity is intended. If not, choose a shallow, deep, or replacement operation from the exact mutation path, then add an `is not` assertion for the boundary that must be new.
  Source: [Shallow and deep copy](https://codewiki.com/python/copy/)
- `dict.copy()` and `copy.copy()` create a new dictionary, but a nested list, set, dictionary, or instance remains shared.
  Why: Tests that change only top-level keys can pass while production later mutates a shared child. Fix: exercise a nested mutation in the test. If only one known child needs independence, copy that child explicitly; use `deepcopy()` only when the broader reachable graph should follow deep-copy semantics.
  Source: [Shallow and deep copy](https://codewiki.com/python/copy/)
- Do not assume this is safe: a deep copy may duplicate identity-sensitive domain objects, retain an unsuitable copying policy, or fail on a file, socket, lock, frame, or similar resource.
  Why: Functions and classes are returned unchanged, so deep does not mean every identity becomes new. Fix: define ownership at the API boundary. Prefer immutable inputs, explicit constructors, or a domain method such as `clone_for_request()` when the class mixes value state with services or resources.
  Source: [Shallow and deep copy](https://codewiki.com/python/copy/)
- A hand-written `__deepcopy__()` that omits the supplied memo, passes a fresh dictionary to each child, or registers the clone after recursion can duplicate shared children or recurse forever on cycles.
  Why: Fix: create the unpopulated clone, store it as `memo[id(self)]`, and pass the same memo to every recursive `copy.deepcopy()` call. Test one repeated child and one back-reference, not only a tree.
  Source: [Shallow and deep copy](https://codewiki.com/python/copy/)
- `copy.replace(record, field=value)` sounds like a copy operation, but unchanged mutable fields can remain shared.
  Why: A frozen data class does not make objects stored in its fields immutable. Fix: use replacement to express named field changes and explicitly copy any changed ownership boundary. Assert the identity of unchanged mutable fields so sharing is a conscious part of the design.
  Source: [Shallow and deep copy](https://codewiki.com/python/copy/)
- A copy and its source normally start with equal values, so `copied == source` says nothing about whether a mutable child is shared.
  Why: A test can report success even though the first nested mutation will cross the intended boundary. Fix: combine value checks with targeted identity assertions and mutation probes. Avoid asserting new identity for immutable leaves unless the domain actually assigns meaning to that identity.
  Source: [Shallow and deep copy](https://codewiki.com/python/copy/)
- Do not assume this is safe: a `save(context, value)` function decorated with `@singledispatch` dispatches on `context`, not `value`.
  Why: Generated code often preserves that signature while registering variants for the type of `value`, sending every call down the wrong path.
  Source: [Single-dispatch generic functions](https://codewiki.com/python/singledispatch/)
- `list[int]` and `dict[str, Value]` carry static type information, but they aren't classes that `singledispatch` can use for `isinstance()`-style runtime selection.
  Why: Passing them to `register` raises `TypeError` during registration.
  Source: [Single-dispatch generic functions](https://codewiki.com/python/singledispatch/)
- A default that blindly calls `str(value)` or returns its input makes a missing registration look successful.
  Why: A new type can silently receive an incomplete serialization result instead of exposing the support gap in tests.
  Source: [Single-dispatch generic functions](https://codewiki.com/python/singledispatch/)
- `bool` matches `int`, strings match some collection abstractions, and one class can be a virtual subclass of several unrelated ABCs.
  Why: Tests containing only exact registered types miss wrong selections and ambiguity errors.
  Source: [Single-dispatch generic functions](https://codewiki.com/python/singledispatch/)
- A plugin's top-level `@generic.register` runs only after that plugin module is imported.
  Why: Automatic imports in development don't prove that a test process, CLI entry point, or production worker loads the same registry.
  Source: [Single-dispatch generic functions](https://codewiki.com/python/singledispatch/)
- `(value)` is only a grouped expression.
  Why: Generated code often treats `tuple(value)` as a one-item wrapper, but a string or another iterable is split into several elements instead.
  Source: [Tuples](https://codewiki.com/python/tuples/)
- A tuple cannot replace a slot, but a list, set, or custom object in that slot may still change.
  Why: Returning a tuple that contains a list as an “immutable snapshot” lets the caller mutate shared state.
  Source: [Tuples](https://codewiki.com/python/tuples/)
- Using every tuple as a dictionary key raises `TypeError` when any element is unhashable.
  Why: A subtler risk is a custom element that supplies a hash while allowing state involved in equality and hashing to change.
  Source: [Tuples](https://codewiki.com/python/tuples/)
- Do not assume this is safe: once a caller writes `value, error = parse()`, it depends on the result producing exactly two values.
  Why: Generated code sometimes returns extra diagnostics on one branch or substitutes `None` for the tuple, so the failure appears only on that path.
  Source: [Tuples](https://codewiki.com/python/tuples/)
- Do not assume this is safe: a call such as `Shipment(123, [], "Paris")` does not automatically enforce domain rules because annotations are present.
  Why: Bad types may propagate until hashing, sequence operations, or serialization exposes them.
  Source: [Tuples](https://codewiki.com/python/tuples/)
- Writing `tags: list[str] = []` in a `NamedTuple` class body makes instances that omit the field reuse one list.
  Why: The outer records are immutable, but mutating the list through one instance becomes visible through another.
  Source: [Tuples](https://codewiki.com/python/tuples/)
- Do not treat assignment as copying creates hidden shared state.
  Why: `backup = settings` only adds another name for the same dictionary. Fix: Use `copy()` or the corresponding constructor when the outer container must be independent. If nested objects must be independent too, state the ownership and sharing boundary before deciding whether `copy.deepcopy()` matches the domain.
  Source: [Variables and data types](https://codewiki.com/python/variables-data-types/)
- Do not assume this is safe: comparing strings or numbers with `is` depends on whether an implementation happened to reuse an object.
  Why: Equal values do not imply identical objects. Fix: Use `==` for value equality and `is` only for identity. Write `value is None` for an absent-value sentinel, and never rely on implementation details such as small-integer caching or string interning.
  Source: [Variables and data types](https://codewiki.com/python/variables-data-types/)
- Conversion functions are easily mistaken for validation rules.
  Why: `bool("false")` is true, `int(3.9)` truncates toward zero, and `isinstance(True, int)` is true as well. Fix: Define the permitted source types, text format, and range before converting. Explicitly exclude `bool` when the domain requires an integer, and do not use one constructor call as the whole input contract.
  Source: [Variables and data types](https://codewiki.com/python/variables-data-types/)
- Using `value or default` for missing data also replaces a valid `0`, `False`, empty string, or empty container.
  Why: The program loses states that were originally distinct. Fix: Compare explicitly with `None` when only `None` means missing. When a dictionary must distinguish an absent key from a false value, use `key in mapping` or a separate sentinel object.
  Source: [Variables and data types](https://codewiki.com/python/variables-data-types/)
- Binary floating-point cannot represent many decimal fractions exactly, so `0.1 + 0.2 == 0.3` is false.
  Why: Direct equality checks or accumulated settlement values can therefore be wrong for money. Fix: Compare measurements with `math.isclose()` and an explicit tolerance. Represent money as integer minor units or construct `decimal.Decimal` from strings; do not pass through an inexact `float` first.
  Source: [Variables and data types](https://codewiki.com/python/variables-data-types/)
- Do not assume this is safe: type annotations do not automatically validate function arguments, JSON fields, or configuration values.
  Why: A function annotated with `payload: dict[str, int]` can still receive a string at runtime. Fix: Keep annotations for static checking and tooling, and perform runtime validation where untrusted data enters the system. Test wrong types, not only ideal input.
  Source: [Variables and data types](https://codewiki.com/python/variables-data-types/)
