Skip to content

How to store lists, sets and dicts

A field can hold a list, a tuple[T, ...], a set, a frozenset or a dict, or anything you annotate as Sequence, Set or Mapping from collections.abc. Because the box sets aside room for the field when it is created, you say up front how many elements it can hold at most.

1. Declare the field with a capacity in elements

On a collection, Capacity counts elements, not bytes. If the elements need a capacity of their own, such as text, put it on the element type inside:

from typing import Annotated

from sharedbox import Capacity, SharedBox, field

Title = Annotated[str, Capacity(32)]


class Playlist(SharedBox, name="example-playlist"):
    tracks: Annotated[tuple[Title, ...], Capacity(8)] = ()
    tags: Annotated[frozenset[Annotated[str, Capacity(8)]], Capacity(4)] = frozenset()
    plays: Annotated[dict[Title, int], Capacity(8)] = field(default_factory=dict)

Set elements and dict keys are read back into a new set or dict, so their type has to be hashable, meaning Python can use it as a set element or dict key. A list, set, dict, bytearray, TypedDict or record class that isn't frozen can't be a set element or a dict key, and the box class raises TypeError when you define it.

2. Write and read the collection

You assign and read a collection field like any other:

playlist = Playlist()
playlist.tracks = ("intro", "theme")
playlist.plays = {"intro": 3}
print(playlist.tracks, playlist.plays)  # ('intro', 'theme') {'intro': 3}

Every read gives you a new collection of the declared type. A field annotated as Sequence comes back as a list, a Set as a set and a Mapping as a dict. Order is kept: a set in the order you iterated it, a dict in insertion order.

3. Add an element

A collection you read is a copy, so appending to it changes nothing in the box. Build the new collection and assign it back:

playlist.tracks = (*playlist.tracks, "outro")
print(len(playlist.tracks))  # 3

If you annotate the field as tuple[T, ...] or Sequence[T], a type checker will point out an append that would only change the copy.

4. Handle a collection that is too large

Assigning more elements than the capacity raises ValueError, and the field keeps its old value:

try:
    playlist.tags = frozenset({"a", "b", "c", "d", "e"})
except ValueError as error:
    print(error)  # Playlist.tags holds at most 4 elements; the value has 5
The whole script
"""The script of the guide "How to store lists, sets and dicts"."""

from typing import Annotated

from sharedbox import Capacity, SharedBox, field

Title = Annotated[str, Capacity(32)]


class Playlist(SharedBox, name="example-playlist"):
    tracks: Annotated[tuple[Title, ...], Capacity(8)] = ()
    tags: Annotated[frozenset[Annotated[str, Capacity(8)]], Capacity(4)] = frozenset()
    plays: Annotated[dict[Title, int], Capacity(8)] = field(default_factory=dict)



playlist = Playlist()
playlist.tracks = ("intro", "theme")
playlist.plays = {"intro": 3}
print(playlist.tracks, playlist.plays)  # ('intro', 'theme') {'intro': 3}

playlist.tracks = (*playlist.tracks, "outro")
print(len(playlist.tracks))  # 3

try:
    playlist.tags = frozenset({"a", "b", "c", "d", "e"})
except ValueError as error:
    print(error)  # Playlist.tags holds at most 4 elements; the value has 5

playlist.close()
Playlist.unlink()