How to get the first item of a Python iterable
Hereās a succinct definition of a first()
function for Python:
# Ā© 2024 Alex DeLorenzo. Licensed under the LGPLv3.
from typing import Iterable, TypeVar
T = TypeVar('T')
U = TypeVar('U')
Item = T | U | None
def first(iterable: Iterable[T], default: Item = None) -> Item:
iterator = iter(iterable)
return next(iterator, default)
Scroll to the bottom or click here to see an async implementation of this first()
function.
Using the function
Hereās a look at how to use first()
with various data structures that are common in Python.
Iterables
gen_nums: Iterable[int] = range(10)
first_num: int = next(gen_nums) # 0
assert first(gen_nums) == first_num
Ordered sequences
nums: list[int] = list(gen_nums)
assert first(nums) == first_num
Empty sequences
empty: tuple = ()
assert first(empty) is None
assert first(range(0)) is None
Default value
The default value can be any object, not just None
.
class Missing: pass
assert first(empty, Missing) is Missing
Generator expressions
assert first(num for num in gen_nums) == first_num
Strings and bytes
assert first('example') == 'e'
assert first(b'example') == b'e'
Arrays
from array import array
nums: Iterable[int] = array('i', gen_nums)
assert first(nums) == first_num
Files
from pathlib import Path
nums = map(str, gen_nums)
text = '\n'.join(nums)
path = Path('example.txt')
path.write_text(text)
with path.open() as file:
assert first(file) == f'{first_num}\n'
Custom iterators
from typing import Iterator
from dataclasses import dataclass
@dataclass
class Increment(Iterator[int]):
val: int = gen_nums.start
def __next__(self) -> int:
self.val += gen_nums.step
return self.val
nums: Iterable[int] = Increment()
assert first(nums) == first_num
Unordered collections
Doesnāt make much sense with unordered collections, but it works. It could be used as a way to get a member of a set
without raising a KeyError
if itās empty, or to supply a default value.
nums: set[int] = set(gen_nums)
assert first(nums) is not None
An async first function
from typing import AsyncIterable
async def first(iterable: AsyncIterable[T], default: Item = None) -> Item:
aiterator = aiter(iterable)
return await anext(aiterator, default)
Usage
To run these examples with top-level await
expressions, you can use an async REPL by running python -m asyncio
, or by using IPython.
Files
Hereās an example using š aiopath
, an async pathlib
replacement for Python.
from aiopath import AsyncPath
nums = map(str, gen_nums)
text = '\n'.join(nums)
path = AsyncPath('example.txt')
await path.write_text(text)
async with path.open() as file:
assert await first(file) == f'{first_num}\n'
Async iterables
async def agen_nums() -> AsyncIterable[int]:
for num in gen_nums:
yield num
nums = agen_nums()
assert await first(nums) == first_num
Async generator expressions
nums: AsyncIterable[int] = agen_nums()
assert await first(num async for num in nums) == first_num
Custom async iterators
from typing import AsyncIterator
from dataclasses import dataclass
@dataclass
class Increment(AsyncIterator[int]):
val: int = gen_nums.start
async def __anext__(self) -> int:
self.val += gen_nums.step
return self.val
nums: AsyncIterable[int] = Increment()
assert await first(nums) == first_num