Skip to content

Instantly share code, notes, and snippets.

@zachvictor
Last active March 12, 2025 20:55
Show Gist options
  • Save zachvictor/f4879c2b621a844b18437e57f99a7607 to your computer and use it in GitHub Desktop.
Save zachvictor/f4879c2b621a844b18437e57f99a7607 to your computer and use it in GitHub Desktop.
Python generator to iterate any sequence or mapping (list, dict, etc.) as (key/index, value) pairs
from collections.abc import Mapping, Sequence
from typing import Any, Iterator, Protocol, Tuple, TypeVar, runtime_checkable
K = TypeVar('K') # Key type for mappings
V = TypeVar('V') # Value type
@runtime_checkable
class SupportsItems(Protocol[K, V]):
def items(self) -> Iterator[Tuple[K, V]]: ...
@runtime_checkable
class SupportsIndexing(Protocol[V]):
def __len__(self) -> int: ...
def __getitem__(self, index: int) -> V: ...
def iterate_collection(collection: Any) -> Iterator[Tuple[Any, Any]]:
"""
Iterate over any collection returning (key/index, value) tuples.
Prioritizes standard ABC inheritance checks, then falls back to Protocol checks.
Mimics Go's `for k, v := range ...` behavior.
Examples:
>>> # Iterate over a dictionary
>>> for key, value in iterate_collection({"a": 1, "b": 2}):
... print(f"{key}: {value}")
a: 1
b: 2
>>> # Iterate over a list
>>> for index, value in iterate_collection([10, 20, 30]):
... print(f"Element {index} is {value}")
Element 0 is 10
Element 1 is 20
Element 2 is 30
>>> # Process characters in a string with their positions
>>> for pos, char in iterate_collection("abc"):
... print(f"Character at position {pos}: {char}")
Character at position 0: a
Character at position 1: b
Character at position 2: c
Args:
collection: A Mapping, Sequence, string, or any object that supports
either .items() or indexing with __len__
Yields:
tuple: (key/index, value) pairs
Raises:
TypeError: If the collection doesn't match any supported type or protocol
"""
# First try standard ABC checks (checking inheritance is faster than protocol checks)
if isinstance(collection, Mapping):
yield from collection.items()
elif isinstance(collection, Sequence):
yield from enumerate(collection)
# Fall back to protocol checks for duck-typing compatibility
elif isinstance(collection, SupportsItems):
yield from collection.items()
elif isinstance(collection, SupportsIndexing):
for i in range(len(collection)):
yield i, collection[i]
else:
raise TypeError(
f"Unsupported collection type: {type(collection).__name__} "
"(must be a Mapping, Sequence, or support .items() or indexing)")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment