Home/Python/Data Types II

Data Types II

Lists, tuples, sets, dictionaries, and ownership

Lists

A list is an ordered, mutable sequence. It supports duplicates, indexing, slicing, and membership checks:

Python
regions = ["mumbai", "singapore", "frankfurt"]
print(regions[0], regions[-1])         # mumbai frankfurt
print(regions[:2])                    # ['mumbai', 'singapore']
print("singapore" in regions, len(regions))  # True 3
regions[1] = "tokyo"
print(regions)                        # ['mumbai', 'tokyo', 'frankfurt']

A slice makes a new outer list containing the selected references. It can be empty or shorter than requested; indexing a missing position raises IndexError.

OperationBehavior
items.append(value)Add one value. Another list is added as one element.
items.extend(other)Add the elements of another iterable, such as a list.
items.insert(index, value)Insert before a position.
items.pop()Remove and return the last element; pop(index) selects a position.
items.remove(value)Remove the first equal value; raise ValueError if absent.
items.copy()Copy the outer list, retaining its element references.
items.sort()Sort in place; return None.
sorted(items)Return a new sorted list.

Routing attempts are order-sensitive; duplicates can represent retries:

Python
attempts = ["old-region", "singapore"]
attempts.insert(0, "mumbai")
attempts.extend(["tokyo", "singapore"])
attempts.remove("old-region")
last_attempt = attempts.pop()
print(attempts)     # ['mumbai', 'singapore', 'tokyo']
print(last_attempt)  # singapore

Sorting would change that routing order. Popping an empty list raises IndexError.

Tuples

A tuple is an ordered sequence whose element references cannot be replaced. Use it for a small, fixed record:

Python
target = ("payments", "staging")
service, environment = target  # Unpack the two values
print(service, environment)    # payments staging
print(target == ("payments", "staging"))  # True
print(("payments",))           # ('payments',): one-element tuple

The comma creates the one-element tuple; ("payments") is just a string. Use a list when elements need to be added, removed, or reordered. A dictionary or named record suits data with many fields.

Sets

Sets contain unique, hashable values and support membership checks, union (|), intersection (&), and difference (-). They do not promise a meaningful iteration order.

Python
required = {"deploy", "read-logs"}
available = {"deploy", "read-metrics"}
print(sorted(required & available))  # ['deploy']
print(sorted(required - available))  # ['read-logs']
print(sorted(required | available))  # ['deploy', 'read-logs', 'read-metrics']

Intersection finds required permissions the caller has; difference finds missing permissions. Union combines both groups and would grant permissions the caller may lack. sorted() makes the display predictable.

  • set() creates an empty set; {} creates a dictionary.
  • add(value) adds a member; duplicates have no effect.
  • remove(value) raises KeyError if absent; discard(value) is harmless when absent.
  • copy() creates an independent outer set.

Hashable values have a stable hash for lookup. Strings and integers work; ordinary lists and dictionaries do not. A tuple is hashable only when all its elements are hashable. Custom equality and hashing are covered later.

Dictionaries

A dictionary maps unique, hashable keys to values. Values can include other containers:

Python
settings = {"workers": 2, "enabled": True, "fallback": None}
settings["workers"] = 4
settings.update({"enabled": False, "region": "mumbai"})
print(settings["workers"], settings["enabled"])  # 4 False
print(settings.get("fallback", "automatic"))    # None
print(settings.get("missing", "automatic"))     # automatic
print("fallback" in settings, "missing" in settings)  # True False
del settings["region"]
print(list(settings.keys()))  # ['workers', 'enabled', 'fallback']

update() replaces matching entries and adds new ones. get(key, default) uses the default only for a missing key; a stored None, False, or 0 remains a value. Use key in mapping to distinguish missing from present.

mapping[key] raises KeyError if absent. del mapping[key] deletes an existing entry; mapping.pop(key, default) removes and returns a value, using the default if absent.

keys(), values(), and items() return live views. Convert a view to a list for a snapshot. Dictionaries preserve insertion order: updating a key keeps its position; deleting and reinserting moves it to the end.

Object references and copies

Assignment creates another reference, not a copy. A shallow copy creates a new outer container while sharing its elements:

Python
defaults = {"workers": 2, "labels": ["api"]}
alias = defaults
candidate = defaults.copy()
candidate["workers"] = 4
candidate["labels"].append("canary")
print(alias is defaults, candidate is defaults)  # True False
print(defaults)   # {'workers': 2, 'labels': ['api', 'canary']}
print(candidate)  # {'workers': 4, 'labels': ['api', 'canary']}

Replacing candidate["workers"] affects only the copied dictionary. Appending to the shared label list affects both. Copy each mutable level the caller needs to own; immutable strings can remain shared.

Example: an independent deployment preview

Copy the label list before editing it, and use set difference to report missing permissions:

Python
defaults = {"workers": 2, "labels": ["api"]}
candidate = defaults.copy()
candidate["labels"] = defaults["labels"].copy()
candidate["labels"].append("canary")
candidate["workers"] = 4

preview = {"target": ("payments", "staging"), "settings": candidate}
required = {"deploy", "read-logs"}
available = {"deploy"}

print(preview["target"])    # ('payments', 'staging')
print(preview["settings"])  # {'workers': 4, 'labels': ['api', 'canary']}
print(sorted(required - available))  # ['read-logs']
print(defaults)            # {'workers': 2, 'labels': ['api']}

The target is a fixed pair, settings use named fields, labels remain editable, and permission checks use sets. The defaults remain independent of the preview's edits.

Gotchas

Repeated references and nested mutability

Multiplication repeats references; a tuple can still refer to mutable data:

Python
groups = [[]] * 3
groups[0].append("canary")
print(groups)  # [['canary'], ['canary'], ['canary']]

record = ("payments", ["api"])
record[1].append("canary")
print(record)  # ('payments', ['api', 'canary'])

The three group entries share one list. The tuple's references stay fixed, but the list it refers to can change. For independent groups, create separate lists.

Mutation methods return None

Methods such as sort(), append(), and dict.update() mutate their receiver and return None:

Python
labels = ["worker", "api"]
result = labels.sort()
print(labels, result)  # ['api', 'worker'] None

labels = ["worker", "api"]
ordered = sorted(labels)
print(labels, ordered)  # ['worker', 'api'] ['api', 'worker']

Use sort() to change an existing list's order; use sorted() to retain the original. Methods that return a removed value, such as pop(), have a different contract.

Operation costs

In CPython, lists are resizable arrays of object references:

  • Index access is constant-time; end append is amortized constant-time.
  • Membership can scan the whole list; front insertion/removal shifts references. Both are linear in list length.
  • Dictionary and set lookup is usually average constant-time, with memory overhead and worst-case exceptions.

A list of integers also references Python integer objects. The standard library's array type offers compact, homogeneous numeric storage. Choose a container for the operations and representation required; lists remain useful for ordered, editable sequences.

Put it into practice

Implement the exercise in a full-screen workspace, then run its tests.

Open practice
Next lessonControl flow