Skip to content

Collections

This page covers Python's built-in collections in d/Python: lists, tuples, dictionaries, sets and ranges. They behave as they do in Python: two names for the same list see the same changes, a tuple has a fixed shape, a dictionary remembers the order you added keys in, and a set holds each value once. d/Python adds one rule of its own today: a list, a dictionary or a set holds values of one type only. The compiler works out that type from your code and generates code tailored to it. After this page you will know which methods work and how to live with the one-type rule.

Lists share identity

numbers = [3, 1, 2]
alias = numbers
alias.append(5)
numbers.sort()
print(numbers, alias is numbers, numbers[0], numbers[-1], numbers[1:3])
numbers += [8]
del numbers[0]
print(alias, len(alias), 8 in alias, alias.index(5), alias.count(2))
copy = numbers.copy()
copy.reverse()
print(copy, numbers, numbers.pop(), numbers)
numbers.insert(0, 42)
numbers.remove(42)
numbers.extend((9, 9))
numbers.clear()
print(numbers, [1] + [2, 3], list("ab"), list(range(3)))

append, clear, copy, extend, reverse, sort, insert, pop, remove, count and index all work, and so do deleting an item with del, slicing, joining lists with +, and +=, which extends the list in place so that every other name for it sees the change. list(...) and extend accept a tuple, a string, a bytes value, a dictionary or one of its views, or a set. list.sort() sorts in place, keeps equal items in their original order (it is stable), and returns None.

The one-element-type rule

mixed = (1, "one", [1])
print(mixed, mixed[1], len(mixed))

A tuple may mix types, because the compiler knows what type sits at each position. A list may not: [1, "one"] makes the compiler stop with the message mixed element types in a Python list need the tagged-value adapter, which means that lists of mixed types are not yet supported. A list holding objects of two different classes you wrote gets the same message. There is one more wrinkle: an empty collection that is printed inside a loop must already have a known element type at that point, and the compiler does not yet look ahead through the whole function to find one. Until it does, the idiom is to start with one typed value and clear it:

items = [0]
items.clear()
for n in range(3):
    print(items)
    items.append(n)

Tuples

point = (3, 4)
x, y = point
(a, b), c = ((1, 2), 3)
print(point, x, y, a, b, c, point + (5,), point == (3, 4), 4 in point)
def bounds(values):
    return min(values), max(values)
low, high = bounds([5, 2, 9])
print(low, high, tuple(point) is point)

Unpacking a returned tuple, nested unpacking, indexing, joining with +, equality, in and printing all work. You can loop over a tuple when every item has the same type. Tuples whose shape is only known while the program runs, and starred targets such as first, *rest = point, are not yet supported.

Dictionaries keep order

counts = {"b": 2, "a": 1}
counts["c"] = counts.get("c", 0) + 3
counts.setdefault("d", 4)
for key, value in counts.items():
    print(key, value)
print(list(counts.keys()), list(counts.values()), "a" in counts, len(counts))
print(counts.pop("d"), counts.popitem())
counts.update({"z": 26})
del counts["a"]
print(counts, dict([("k", 1)]), dict(k=1, j=2), {n: n * n for n in range(4)})
try:
    counts["missing"]
except KeyError as error:
    print("KeyError:", error)

Keys must be values that cannot change: simple values such as numbers and strings, or tuples of them. keys(), values() and items() are live views that reflect later changes to the dictionary. get, setdefault, pop, popitem, clear and update work, and so does building a dictionary from a list of pairs, from keyword arguments, from ** unpacking, or from a comprehension.

Sets

unique = {1, 2, 2, 3}
unique.add(4)
unique.discard(9)
print(sorted(unique), 2 in unique, len(unique))
print(sorted(unique | {9}), sorted(unique & {1, 4}), sorted(unique - {1}), sorted(unique ^ {1, 7}))
print({1, 2} <= unique, {1, 2} < {1, 2}, frozenset([1, 1]) == frozenset([1]))
unique |= {10}
print(sorted(unique), {c for c in "hello"} == set("hello"), sorted(set("aab")))

Python does not promise any particular order when you loop over a set, so the example sorts a set before printing it, and the tests check which values are present rather than the order.

Ranges

big = range(10 ** 20, 10 ** 20 + 10, 3)
print(big, len(big), big[1], big[-1], 10 ** 20 + 3 in big, list(range(5, 0, -2)))
print(range(3) == range(0, 3), range(0).start, range(10).step, bool(range(0)))

A range is an unchangeable object whose start, stop and step can be integers of any size. It never stores its items, so it stays small however far it reaches. len() has an upper limit, fixed in advance, on how large a length it will report; a range longer than that is an error rather than a wrong number.

What the tests cover

Every claim on this page comes from a test program that runs in both d/Python and CPython and must print the same thing. The corpus page lists those programs, with their reviewed output.