Advanced Slicing and Pattern Unpacking
Advanced Slicing and Pattern Unpacking
After reading this chapter, you will master Python’s slicing mechanics ([start:stop:step]), employ reusable slice objects for fixed-width parsing, manipulate negative strides for sequence reversal, execute in-place slice mutations, and destructure variable-length collections with extended starred unpacking.
Mental model
Slicing extracts a subset of a sequence using half-open intervals: [start:stop) where start is inclusive and stop is exclusive.
Index Mapping:
Indices: 0 1 2 3 4 5
Items: [ P Y T H O N ]
-Indices: -6 -5 -4 -3 -2 -1
Slicing Interval: [1:4] -> Extracts indices 1, 2, 3 ('Y', 'T', 'H')
The Stride Equation
When specifying a step (s[start:stop:step]), indices are computed as:
\[\text{index}_k = \text{start} + k \times \text{step} \quad \text{for } k = 0, 1, 2, \dots\]
When step < 0, indexing moves backward from start down to stop (exclusive).
Extended Starred Unpacking
Python allows unpacking arbitrary iterables into target variables, capturing any remaining elements into a list with a starred name (*rest):
Tuple: (10, "auth", 200, 0.45, 128, "OK")
Unpack: (code, svc, *metrics, status)
│ │ │ │
10 "auth" [200, 0.45, 128] "OK"
Minimal example
Save as slicing_and_unpacking.py:
# slicing_and_unpacking.py
def main() -> None:
# 1. Extended starred unpacking
telemetry_packet = ("node-01", "2026-09-07T10:00:00Z", 42.1, 88.4, 12.0, 99.1, "STABLE")
node, timestamp, *metrics, status = telemetry_packet
print(f"Node ID : {node}")
print(f"Timestamp : {timestamp}")
print(f"Metrics : {metrics} (Captured {len(metrics)} sensor values)")
print(f"Status : {status}")
# 2. Named Slice Objects for Fixed-Width Record Parsing
raw_log = "2026-09-07 10:15:00 [ERROR] Connection timeout to database host"
TIMESTAMP = slice(0, 19)
LEVEL = slice(20, 27)
MESSAGE = slice(28, None) # None runs to the end of string
print(f"\nParsed log using slice objects:")
print(f" Time : {raw_log[TIMESTAMP]}")
print(f" Level : {raw_log[LEVEL]}")
print(f" Message : {raw_log[MESSAGE]}")
if __name__ == "__main__":
main()Run via uv run python slicing_and_unpacking.py:
Node ID : node-01
Timestamp : 2026-09-07T10:00:00Z
Metrics : [42.1, 88.4, 12.0, 99.1] (Captured 4 sensor values)
Status : STABLE
Parsed log using slice objects:
Time : 2026-09-07 10:15:00
Level : [ERROR]
Message : Connection timeout to database host
Worked examples
Case 1: In-Place Slice Replacement and Resizing
Unlike immutable sequences (strings and tuples), a list supports in-place assignment to slices. You can replace, shrink, or expand a slice dynamically:
# slice_mutation.py
def mutate_buffer() -> None:
buffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(f"Initial buffer: {buffer}")
# Replace elements at indices 2, 3, 4 with a smaller list (shrinks the buffer)
buffer[2:5] = [99]
print(f"After buffer[2:5] = [99]: {buffer}")
# Replace a single index with multiple elements (expands the buffer)
buffer[5:6] = [700, 800, 900]
print(f"After buffer[5:6] = [700, 800, 900]: {buffer}")
# Clear elements at even indices using extended stride
del buffer[::2]
print(f"After del buffer[::2]: {buffer}")
if __name__ == "__main__":
mutate_buffer()Run:
uv run python slice_mutation.pyOutput:
Initial buffer: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
After buffer[2:5] = [99]: [0, 1, 99, 5, 6, 7, 8, 9]
After buffer[5:6] = [700, 800, 900]: [0, 1, 99, 5, 6, 700, 800, 900, 8, 9]
After del buffer[::2]: [1, 5, 700, 900, 9]
Case 2: Parsing Protocol Frames with Named Slices
When parsing binary or fixed-width protocol headers, hardcoding numerical slice boundaries throughout a codebase creates brittle logic. Storing slice instances in configuration objects keeps parsers readable and maintainable:
# frame_parser.py
class FrameFormat:
MAGIC_BYTES = slice(0, 4)
VERSION = slice(4, 6)
PACKET_ID = slice(6, 10)
PAYLOAD = slice(10, None)
def parse_network_frame(raw_hex: str) -> dict[str, str]:
return {
"magic": raw_hex[FrameFormat.MAGIC_BYTES],
"version": raw_hex[FrameFormat.VERSION],
"packet_id": raw_hex[FrameFormat.PACKET_ID],
"payload": raw_hex[FrameFormat.PAYLOAD],
}
if __name__ == "__main__":
incoming_frame = "4155544801020000002a48656c6c6f20576f726c64"
fields = parse_network_frame(incoming_frame)
for k, v in fields.items():
print(f"Field {k:10} : {v}")Run:
uv run python frame_parser.pyOutput:
Field magic : 41555448
Field version : 0102
Field packet_id : 0000002a
Field payload : 48656c6c6f20576f726c64
Case 3: Starred Head-Tail Splitting for Recursive Processing
Extended unpacking provides clean head-tail deconstruction without index arithmetic:
# pipeline_reducer.py
from collections.abc import Callable
def apply_middleware_pipeline(
payload: str,
middlewares: list[Callable[[str], str]]
) -> str:
if not middlewares:
return payload
# Deconstruct head and remaining tail
current_handler, *remaining_handlers = middlewares
transformed = current_handler(payload)
return apply_middleware_pipeline(transformed, remaining_handlers)
if __name__ == "__main__":
pipeline: list[Callable[[str], str]] = [
lambda s: s.strip(),
lambda s: s.lower(),
lambda s: s.replace(" ", "_"),
lambda s: f"validated://{s}",
]
raw_input = " System Production Cluster Alpha "
processed = apply_middleware_pipeline(raw_input, pipeline)
print(f"Raw input : '{raw_input}'")
print(f"Processed : '{processed}'")Run:
uv run python pipeline_reducer.pyOutput:
Raw input : ' System Production Cluster Alpha '
Processed : 'validated://system_production_cluster_alpha'
Pitfalls
Pitfall 1: Assigning a Non-Iterable to a Slice
Slice assignment replaces a range of items, so the right-hand side must be an iterable:
lst = [1, 2, 3, 4]
# THE BUG:
lst[1:3] = 99 # TypeError: can only assign an iterable
# THE FIX: Wrap the replacement in an iterable (such as a list)
lst[1:3] = [99] # Result: [1, 99, 4]Pitfall 2: Multiple Starred Expressions in a Single Target
You can only have one starred expression in an unpacking assignment. Python cannot resolve ambiguity if multiple wildcards exist:
data = [1, 2, 3, 4, 5]
# THE BUG:
*first, *second = data # SyntaxError: multiple starred expressions in assignment
# VALID:
first, *middle, last = dataExercises
- Given the string
text = "abcdefghijklmnopqrstuvwxyz", write a single slicing expression that extracts every third letter in reverse order. - Given a list
data = [10, 20, 30, 40, 50], write a slice assignment statement that replaces the middle three elements[20, 30, 40]with the single element[999]. - Given a variable-length list containing at least two elements, use extended unpacking to assign the first element to
head, the last element totail, and the remaining items tobody. - Demonstrate why
s[:]creates a shallow copy of a list, and show what happens when the inner elements are mutable lists.
Further reading
- Python Documentation: The
sliceobject and sequence indexing. - PEP 3132: Extended Iterable Unpacking.
- Python Reference Manual: Assignment statements.