RUNTIME VALUE CONVERSIONS

Mutable lists, dictionaries, and tuples

Lists, dictionaries, and tuples may contain different supported value types, including nested containers and class instances. These examples use runtime value conversion.

Share and change a list

def update(values: list[int | str]) -> None:
    values[0] = "changed"
    values.append(9)

def shared_values() -> list[int | str]:
    values: list[int | str] = [1, "two"]
    alias = values
    update(alias)
    return values

shared_values() returns ["changed", "two", 9]. Assignment and function calls keep the same list object; they do not make independent copies.

Supported list operations

FormBehavior
[], [value, ...]Empty or populated lists with mixed and nested supported values.
values[index], values[index] = valueCalculated integer indices; negative indices count from the end.
values.append(value)Add one element to the same list.
extend(iterable), insert(index, value)Add an iterable or insert at a clamped index; self-extension duplicates the original elements once.
pop(index=-1), remove(value), clear()Pop returns the removed element; remove deletes the first equal value. Clear empties the same list. Missing values or indices report errors.
sort(key=None, reverse=False), reverse()Change the same list and return None. Sort accepts keyword arguments only.
copy(), index(value, start, stop)Shallow copy or first matching position within optional integer bounds. Tuple index is also supported.
values += other, values *= countMutate the shared list. The right operand of += may be a list or tuple.
left + right, values * countCreate a new outer list; concatenation requires two lists.
values[start:stop:step]Create a shallow slice, including negative steps.
len, iteration, in/not in, count(value)Inspect elements in sequence order.
==, !=, is, is notCompare list contents or test shared list identity.

Nested mutable elements remain shared in slices, copies, concatenation, and repetition. A new outer container is not a deep copy. Slice assignment and list-item deletion remain unsupported. Use pop/remove/clear for the documented removal operations. Sort is stable, evaluates keys once, and reports mutation of the target during sorting. Copies and slices remain shallow.

def main() -> int:
    values = [3, 1]
    alias = values
    values.extend([2, 1])
    values.insert(0, 4)
    values.remove(1)
    values.sort()
    print(alias, values.pop(), values)
    return 0

Ordered mutable dictionaries

Runtime dictionaries preserve insertion order and shared identity across variables, calls, class fields, and nested containers. Lookup uses a hash index; replacing a value preserves the original key and position. Deleting then reinserting a key appends it at the end.

def word_counts(text: str) -> dict[str, int]:
    counts: dict[str, int] = {}
    for word in text.lower().split():
        counts[word] = counts.get(word, 0) + 1
    return counts

def main() -> int:
    counts = word_counts("Python C Python café CAFÉ")
    for word, count in counts.items():
        print(word, count)
    return 0

This prints python 2, c 1, and café 2 on separate lines in first-seen order.

FormBehavior
{}, {key: value, ...}, dict()Create an ordered dictionary. Duplicate equal keys keep their first position and last value.
dict(source, name=value), d.update(source, name=value)Copy or merge a dictionary or an admitted iterable of key/value pairs, then apply explicit keywords. The source is optional. Update changes the existing object and returns None.
d[key], d[key] = value, del d[key]Read, replace, insert, or delete. Missing reads and deletions report KeyError.
d.get(key, default), d.setdefault(key, default)Read with a fallback, or insert and return the fallback when absent. Omitted defaults are None.
d.pop(key, default), d.popitem()Remove and return a value, or the last inserted key/value tuple. Missing pop without a default and popitem on an empty dictionary report KeyError.
d.copy(), d.clear()Create a shallow independent outer dictionary, or remove all entries from the same object. Nested mutable values remain shared.
len(d), bool(d), key in dCount entries, test nonemptiness, or test key membership.
==, !=, is, is notCompare contents independently of insertion order, or test shared identity.

Keys may be strings, signed 64-bit integers, finite floats, Booleans, None, or recursively hashable tuples of these. Equal numeric keys share an entry: 1, 1.0, and True are equivalent keys. Mutable containers, objects, functions, ranges, and slices are outside the key profile. Values may be any supported runtime value.

Live keys, values, and items

d.keys(), d.values(), and d.items() return live views. Their lengths and future iterations reflect changes to the dictionary. Iterate directly, use membership tests, or materialize a snapshot with list(view) or tuple(view). Iterating a dictionary itself yields keys; items yields key/value tuples.

Replacing existing values during iteration is supported. Adding, deleting, or clearing keys during an active iteration reports RuntimeError, including delete/reinsert operations that keep the size unchanged. This explicit profile is stricter than some Python iterator cases. Iterate over list(d) when changing keys. Views have no indexing, slicing, ordering, equality, or set operations in this profile.

Dictionary unpacking with **, unions with |, fromkeys, custom mapping protocols, reverse iteration, and direct dictionary formatting remain unsupported. Use dict(source) and update() to combine supported data. Sets retain their separate typed conversion forms.

Example: inspect a live view, then remove completed entries safely. Snapshot the keys before changing dictionary structure:

def main() -> int:
    counts = {"done": 0, "todo": 2}
    keys = counts.keys()
    counts["later"] = 1
    print(len(keys))
    for name in list(keys):
        if counts[name] == 0:
            del counts[name]
    for name, count in counts.items():
        print(name, count)
    return 0

Expected output:

3
todo 2
later 1

Different types at tuple positions

def describe(name: str, count: int) -> tuple[str, int, bool]:
    row: tuple[str, int, bool] = (name, count, count > 0)
    return row

tuple[str, int, bool] checks each position separately. Use tuple[int, ...] for a variable-length tuple of integers. Unannotated tuples can also contain mixed supported values.

Immutable tuple slots, shared contents

def nested_values() -> tuple[str, list[int]]:
    values = [1, 2]
    row = ("items", values)
    row[1].append(3)
    return row

The result is ("items", [1, 2, 3]). A tuple slot cannot be replaced, but a list referenced by that slot can change. Tuples support indexing, slices, concatenation with tuples, repetition, equality, membership, count(value), and iteration. Identity tests for numbers, strings, and tuples are unsupported; use equality for value comparisons. is None and is not None are supported for optional values.

Packing, conversion, and unpacking

def unpack_row(row: tuple[str, int]) -> str:
    name, count = row
    return name + ": " + str(count)

Use fixed unpacking targets with a matching element count over any admitted iterable, including dictionary keys and views. Starred targets are unsupported. list(value) and tuple(value) accept a supported string, list, tuple, range, dictionary, or dictionary view. A string yields one-character strings. list() and tuple(), or [] and (), create empty containers.

Container annotation checks

list[int | str] accepts integer and string elements at an annotated boundary. These checks inspect the current contents; they do not attach a permanent element schema to the shared object. A later write through an alias can change contents, and the next annotated boundary checks them again. See Function and assignment checks.

Container growth is constrained by the runtime context's memory and work limits. dict[K, V] checks every current key and value at annotated boundaries. Sets use the separate typed conversion forms.