How to Filter Data in Python with Type Hints
A reusable filter_data helper uses optional predicates and numeric bounds with modern Python type hints.
Python code
35 linesfrom typing import Iterable, TypeVar, Callable, Any
T = TypeVar("T")
def filter_data(
items: Iterable[T],
predicate: Callable[[T], bool] | None = None,
*,
min_value: float | None = None,
max_value: float | None = None,
) -> list[T]:
"""Filter items by predicate and/or numeric bounds."""
result: list[T] = []
for item in items:
if predicate is not None and not predicate(item):
continue
if isinstance(item, (int, float)):
if min_value is not None and item < min_value:
continue
if max_value is not None and item > max_value:
continue
result.append(item)
return result
if __name__ == "__main__":
numbers = [1, 5, 8, 12, 20, 3, 7]
even_numbers = filter_data(numbers, predicate=lambda x: x % 2 == 0)
print(f"Even numbers: {even_numbers}")
bounded = filter_data(numbers, min_value=5, max_value=15)
print(f"Between 5 and 15: {bounded}")
combined = filter_data(numbers, predicate=lambda x: x > 4, max_value=10)
print(f"Greater than 4 and at most 10: {combined}")
Output
Even numbers: [8, 12, 20]
Between 5 and 15: [5, 8, 12, 7]
Greater than 4 and at most 10: [5, 8, 7]
How it works
The filter_data function accepts an iterable and an optional predicate callable. It uses TypeVar to keep the return type consistent with the input element type. Numeric bounds are checked only for int and float items, leaving other types unaffected. The * in the signature makes min_value and max_value keyword-only, preventing accidental positional misuse. The function returns a new list, leaving the original data unchanged.
Common mistakes
- Checking numeric bounds on non-numeric items without an isinstance guard
- Forgetting the keyword-only marker `*` before min_value/max_value
- Assuming the predicate is always provided; it defaults to None
- Returning a generator instead of a list when a list is expected
Variations
- Use a list comprehension with conditionals for simpler one-off filtering.
- Leverage `functools.partial` or `lambda` wrappers for more complex reusable predicates.
Real-world use cases
- Sanitizing API response data by keeping only numeric values within a valid range before further processing.
- Building a generic utility in a data pipeline to filter rows by user-specified criteria without hardcoding each case.
- Creating a validation helper in an ETL process to exclude outliers from sensor readings before analytics.
Sponsored
More from Testing & modern typing
- Capture stdout and stderr with pytest capsys easy
- Characterization Test for Legacy Python Code medium
- Dataclass with Type Hints Fields in Python easy
- Dependency Injection in Python for Testability easy
- Design Data Helpers with Python TypedDict and Literal easy
- Fix and Test a Regression Bug in Python with Unit Tests easy
Keep learning
Related tutorials and quizzes for this topic.