Iterator API
Overview
The iterator API provides a generator-shaped facade over offset-preserving segmentation.
The current implementation computes the complete result of split_with_offsets() before
yielding, so it does not yet provide incremental parsing or bounded memory.
This is particularly useful for:
Pipeline integration: Feed a list-backed result into iterator-oriented stages
Document order: Consume segments through a standard iterator interface
Future compatibility: Adopt incremental behavior later without changing callers
Iterator Function
from phrasplit import iter_split_with_offsets
for segment in iter_split_with_offsets(text, mode="sentence"):
print(f"{segment.id}: {segment.text}")
The iterator signature matches split_with_offsets():
iter_split_with_offsets(
text: str,
*,
mode: str = "sentence",
use_spacy: bool | None = None,
language_model: str = "en_core_web_sm",
max_chars: int | None = None,
) -> Iterator[SplitSegment]
Guarantees
Document Order
Segments are always yielded in document order (by char_start):
segments = []
for seg in iter_split_with_offsets(text):
if segments:
# Current segment always comes after previous
assert seg.char_start >= segments[-1].char_start
segments.append(seg)
Backend state
Each iterator has its own result sequence, but model loading and CLI diagnostics use the module-level state documented by the implementation:
# Safe to run multiple iterators
iter1 = iter_split_with_offsets(text1)
iter2 = iter_split_with_offsets(text2)
# Results remain independent even though model caches may be reused
seg1 = next(iter1)
seg2 = next(iter2)
Same Offsets
Offset guarantees are identical to split_with_offsets():
for segment in iter_split_with_offsets(text):
# Offsets map exactly to original text
assert text[segment.char_start:segment.char_end] == segment.text
Usage Examples
Basic Streaming
from phrasplit import iter_split_with_offsets
text = "First sentence. Second sentence.\\n\\nNew paragraph."
for segment in iter_split_with_offsets(text, mode="sentence"):
print(f"Processing {segment.id}: {segment.text}")
# Process immediately without waiting for all segments
Real-time TTS Pipeline
import asyncio
from phrasplit import iter_split_with_offsets
async def synthesize_segment(segment):
"""Synthesize audio for one segment."""
# Hypothetical TTS call
audio = await tts_engine.synthesize(segment.text)
return {
"segment_id": segment.id,
"audio": audio,
"position": (segment.char_start, segment.char_end)
}
async def stream_synthesis(text):
"""Stream synthesis results as soon as each segment is ready."""
for segment in iter_split_with_offsets(text, mode="sentence"):
result = await synthesize_segment(segment)
yield result
# Usage
async for audio_chunk in stream_synthesis(long_text):
await audio_output.write(audio_chunk)
Processing Large Files
def process_large_book(file_path, output_path):
"""Process a large ebook segment by segment."""
with open(file_path, 'r', encoding='utf-8') as f:
text = f.read()
with open(output_path, 'w', encoding='utf-8') as out:
for segment in iter_split_with_offsets(text, mode="sentence"):
# Process one segment at a time
processed = process_segment(segment)
# Write immediately
out.write(f"{segment.id}\\t{processed}\\n")
# Memory is freed after each iteration
Parallel Processing
from concurrent.futures import ThreadPoolExecutor
from phrasplit import iter_split_with_offsets
def process_batch(segments):
"""Process a batch of segments."""
results = []
for seg in segments:
result = expensive_operation(seg.text)
results.append((seg.id, result))
return results
def stream_with_batching(text, batch_size=10):
"""Stream segments in batches for parallel processing."""
batch = []
for segment in iter_split_with_offsets(text, mode="sentence"):
batch.append(segment)
if len(batch) >= batch_size:
yield batch
batch = []
# Yield remaining
if batch:
yield batch
# Process batches in parallel
with ThreadPoolExecutor(max_workers=4) as executor:
for batch in stream_with_batching(long_text, batch_size=20):
future = executor.submit(process_batch, batch)
results = future.result()
# Handle results...
Progress Tracking
from phrasplit import iter_split_with_offsets
def process_with_progress(text):
"""Process with progress tracking."""
total_chars = len(text)
for segment in iter_split_with_offsets(text, mode="sentence"):
# Calculate progress based on character position
progress = (segment.char_end / total_chars) * 100
print(f"Progress: {progress:.1f}% - Processing {segment.id}")
# Process segment...
process_segment(segment)
Selective Processing
def process_matching_segments(text, pattern):
"""Process only segments matching a pattern."""
import re
for segment in iter_split_with_offsets(text, mode="sentence"):
if re.search(pattern, segment.text):
# Only process matching segments
result = expensive_processing(segment)
yield (segment.id, result)
else:
# Skip non-matching segments
yield (segment.id, None)
Early Termination
def find_first_mention(text, search_term):
"""Find first segment containing search term."""
for segment in iter_split_with_offsets(text, mode="sentence"):
if search_term in segment.text:
# Found it - stop iteration
return segment
return None
# Iterator stops as soon as we return
result = find_first_mention(long_document, "important phrase")
Comparison with List Version
When to Use Iterator
โ
Use iter_split_with_offsets() when an iterator interface fits the integration and
the input is small enough for list-backed segmentation.
When to Use List
โ
Use split_with_offsets() when:
Need random access to segments
Need total segment count upfront
Performing multiple passes over segments
Text size is small (< 1 MB)
Need to sort or filter all segments
Performance Characteristics
Memory Usage
# List version - loads all segments into memory
segments = split_with_offsets(huge_text) # Uses O(n) memory
# Iterator facade - the underlying list is materialized before yielding
for seg in iter_split_with_offsets(text):
process(seg)
Time to First Segment
The iterator has the same time-to-first-segment and memory characteristics as the list version since the current implementation processes all segments upfront. A future optimization could make this truly incremental.
Note
Current implementation note: The iterator currently uses split_with_offsets() internally and yields from the result. A future version may implement true streaming for faster time-to-first-segment.
Overhead
Iterator overhead is minimal:
# List conversion
segments_list = list(iter_split_with_offsets(text))
# Equivalent to
segments_list = split_with_offsets(text)
Best Practices
Process Through the Iterator Interface
# Good - process each yielded segment for seg in iter_split_with_offsets(text): result = process(seg) save(result) # This is equivalent to the current implementation's internal materialization all_segments = list(iter_split_with_offsets(text))
Use with Context Managers
with open('output.txt', 'w') as f: for seg in iter_split_with_offsets(text, mode="sentence"): processed = process(seg) f.write(f"{processed}\\n")
Combine with Generators
def processed_segments(text): """Generator pipeline.""" for seg in iter_split_with_offsets(text): if should_process(seg): yield process(seg) # Chain generators for result in processed_segments(text): output(result)
See Also
Offset-Preserving Segmentation - Offset coordinate system and guarantees
API Reference - Complete API reference
Examples - More usage examples