Add your description here
Project description
iterable-extensions
Collection of useful extension methods to Iterables to enable functional programming. It is heavily inspired by C#'s LINQ.
The extension methods are implemented using https://pypi.org/project/extensionmethods/. Under the hood they rely strongly on itertools and are basically syntactic sugar to write code in a more functional style.
Type-checking is supported, see Type-checking.
Important notes:
- Whereas
itertoolsgenerally returns iterators that are exhausted after consuming once,iterable-extensionsgenerally returns iterables that may be consumed repeatedly. - Similarly to
itertools,iterable-extensionsaims to evaluate lazily so that not the entire input iterable is loaded into memory. However, there are some notable exceptions, including:order_byandorder_by_descendinggroup_by
This package is under development. Only a number of extension methods are currently implemented.
For the full API reference and the currently implemented methods, see https://iterable-extensions.readthedocs.io/.
Example usage
To filter elements in an iterable based on some predicate:
from iterable_extensions import where, to_list
source = [1, 2, 3, 4, 5]
filtered = source | where[int](lambda x: x > 3) # Only numbers greater than 3
lst = filtered | to_list() # Materialize into list
print(lst)
# [4, 5]
lst2 = filtered | to_list() # Iterables can be consumed multiple times
print(lst2)
# [4, 5]
To transform elements according to some function:
from iterable_extensions import select, to_list
source = [1, 2, 3, 4, 5]
transformed = source | select[int, str](lambda x: str(2 * x)) # Transform each element
lst = transformed | to_list() # Materialize into list
print(lst)
# ['2', '4', '6', '8', '10']
To group elements based on a key:
from dataclasses import dataclass
from iterable_extensions.iterable_extensions import group_by
@dataclass
class Person:
age: int
name: str
source = [
Person(21, Gender.MALE, "Arthur"),
Person(37, Gender.FEMALE, "Becky"),
Person(12, Gender.MALE, "Chris"),
Person(48, Gender.MALE, "Dave"),
Person(88, Gender.MALE, "Eduardo"),
Person(56, Gender.FEMALE, "Felice"),
]
grouped = source | group_by[Person, int](lambda p: p.age) # Group by age
lst = grouped | to_list() # Materialize into list
print(lst)
# [
# 10: [Person(age=10, name='Arthur'), Person(age=10, name='Becky')],
# 20: [Person(age=20, name='Chris')],
# 30: [Person(age=30, name='Dave'), Person(age=30, name='Eduardo'), Person(age=30, name='Felice')]
# ]
You can chain these methods into functional-style code. For instance, in the below example, to get the full name of the oldest male and female:
from dataclasses import dataclass
from enum import IntEnum
from iterable_extensions.iterable_extensions import (
Grouping,
first,
group_by,
order_by_descending,
select,
to_list,
)
class Gender(IntEnum):
MALE = 1
FEMALE = 2
@dataclass
class Person:
age: int
gender: Gender
first_name: str
last_name: str
data = [
Person(21, Gender.MALE, "Arthur", "Johnson"),
Person(56, Gender.FEMALE, "Becky", "de Vries"),
Person(12, Gender.MALE, "Chris", "Lamarck"),
Person(48, Gender.MALE, "Dave", "Stevens"),
Person(88, Gender.MALE, "Eduardo", "Doe"),
Person(37, Gender.FEMALE, "Felice", "van Halen"),
]
grouped = (
data
| group_by[Person, Gender](lambda p: p.gender) # Group by gender
| select[Grouping[Person, Gender], Person]( # Within each group
lambda g: (
g
# Order by age, descending
| order_by_descending[Person, int](lambda p: p.age)
| first() # Take the first entry
)
# For each gender, aggregate first and last name
| select[Person, str](lambda p: f"{p.first_name} {p.last_name}")
| to_list() # Materialize into list
)
print(grouped)
# ['Eduardo Doe', 'Becky de Vries']
Type-checking
The iterable extensions are fully type-annotated and support type inference with
linters as much as possible. However, due to limitations in current type checkers,
inference doesn't propage through the | operator. For example:
source: list[int] = [1, 2, 3, 4, 5]
filtered = source | where(lambda x: x > 3)
Will give an error like Operator ">" not supported for types "T@where" and "Literal[3]" on the lambda body, even though the type of x is fully specified through source.
To circumvent this, you can expicitly specify its type: where[int](lambda x: ...). This also gives you autocompletion on x in the lambda body.
Alternatively, you can explicitly define the function instead of writing a lambda:
def func(x: int) -> bool:
return x > 3
filtered = source | where(func)
Although this hampers the readability of the functional style that the iterable-extensions package aims to provide.
Note that the type annotations are only for static checkers. You can ignore these errors and the code will still run fine.
How to read Extension[TIn, **P, TOut]
In the API reference, you'll notice that all extension methods inherit from Extension[TIn, **P, TOut]. This class is the core of the extensionsmethods package (https://pypi.org/project/extensionmethods/). It provides the basic |-operator functionality.
The Extension class has two type parameters and a paramspec:
TIn: The type that the extension is defined to operate on.**P: Arbitrary number of arguments that the extension method may take.TOut: The type of the return value of the extension method.
For example, looking at the signature of select:
class select[TIn, TOut](
Extension[
Iterable[TIn],
[Callable[[TIn], TOut]],
Iterable[TOut]
]
): ...
we see that:
TIn=Iterable[TIn].selectis defined to operate on iterables of an arbitrary input type.**P=[Callable[[TIn], TOut]].selectrequires a mapping function as a parameter.TOut=Iterable[TOut].selectreturns an iterable of an arbitrary output type.
For example:
source: list[int] = [1, 2, 3, 4]
# Allowed. Inputs ints, outputs strings.
source | select[int, str](lambda: str(x))
# Not allowed. Expects strings as inputs, but ints are given.
source | select[str, str](lambda: str(x))
# Not allowed. Excepts ints as output, but the lambda returns strings.
source | select[int, int](lambda: str(x))
Installation
Using pip:
pip install iterable-extensions
Using uv:
uv add iterable-extensions
License
This project is licensed under the MIT License. See the LICENSE file for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file iterable_extensions-0.1.0.tar.gz.
File metadata
- Download URL: iterable_extensions-0.1.0.tar.gz
- Upload date:
- Size: 60.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd818c742f37d6e9886b98245f000580a9b0a534688e4ad99ec056685f697585
|
|
| MD5 |
339d2f418f2c3b08ede4f00e9d44f506
|
|
| BLAKE2b-256 |
c80bd8c1d342e055f1dce811e11d3ffb25f5c3d077d50ab57152d6fb5ab3a363
|
Provenance
The following attestation bundles were made for iterable_extensions-0.1.0.tar.gz:
Publisher:
build-test-publish.yml on Pim-Mostert/iterable-extensions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
iterable_extensions-0.1.0.tar.gz -
Subject digest:
bd818c742f37d6e9886b98245f000580a9b0a534688e4ad99ec056685f697585 - Sigstore transparency entry: 963308107
- Sigstore integration time:
-
Permalink:
Pim-Mostert/iterable-extensions@730b7d3734529364b6dc11732c9c4c365755b7ff -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Pim-Mostert
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build-test-publish.yml@730b7d3734529364b6dc11732c9c4c365755b7ff -
Trigger Event:
push
-
Statement type:
File details
Details for the file iterable_extensions-0.1.0-py3-none-any.whl.
File metadata
- Download URL: iterable_extensions-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89af0de666bce10cad5a90f4c0a3b086027934292dc512ad1cb1c1534e90756c
|
|
| MD5 |
d534829ef9e71e62b948996b1f2ce3d5
|
|
| BLAKE2b-256 |
4c43d98f0c4f7e8019ddc8ae5c4545943717057cfbbba40d5c64a49af0c684c9
|
Provenance
The following attestation bundles were made for iterable_extensions-0.1.0-py3-none-any.whl:
Publisher:
build-test-publish.yml on Pim-Mostert/iterable-extensions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
iterable_extensions-0.1.0-py3-none-any.whl -
Subject digest:
89af0de666bce10cad5a90f4c0a3b086027934292dc512ad1cb1c1534e90756c - Sigstore transparency entry: 963308110
- Sigstore integration time:
-
Permalink:
Pim-Mostert/iterable-extensions@730b7d3734529364b6dc11732c9c4c365755b7ff -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Pim-Mostert
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build-test-publish.yml@730b7d3734529364b6dc11732c9c4c365755b7ff -
Trigger Event:
push
-
Statement type: