Skip to content

Commit 7fbbd9d

Browse files
committed
Add switch_map operator and equivalent starred and indexed
This draws from the definition in rxjs and maintains parity with the map operator and its variants
1 parent a227802 commit 7fbbd9d

File tree

5 files changed

+1151
-3
lines changed

5 files changed

+1151
-3
lines changed

reactivex/operators/__init__.py

+212-1
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@
2626
Observable,
2727
abc,
2828
compose,
29+
of,
2930
typing,
3031
)
3132
from reactivex.internal.basic import identity
32-
from reactivex.internal.utils import NotSet
33+
from reactivex.internal.utils import NotSet, infinite
3334
from reactivex.subject import Subject
3435
from reactivex.typing import (
3536
Accumulator,
@@ -3325,6 +3326,212 @@ def switch_latest() -> Callable[
33253326
return switch_latest_()
33263327

33273328

3329+
def switch_map(
3330+
mapper: Optional[Mapper[_T1, Observable[_T2]]] = None
3331+
) -> Callable[[Observable[_T1]], Observable[_T2]]:
3332+
"""
3333+
The switch_map operator.
3334+
3335+
Project each element of an observable sequence into a new observable.
3336+
3337+
.. marble::
3338+
:alt: switch_map
3339+
3340+
---1---2---3--->
3341+
[ switch_map(i: of(i, i ** 2, i ** 3)) ]
3342+
---1---1---1---2---4---8---3---9---27--->
3343+
3344+
Example:
3345+
>>> switch_map(lambda value: of(value, value // 2))
3346+
3347+
Args:
3348+
mapper: A transform function to apply to each source element.
3349+
3350+
Returns:
3351+
A partially applied operator function that takes an observable
3352+
source and returns an observable sequence whose elements are
3353+
each element of the result of invoking the transform function
3354+
on each element of the source.
3355+
"""
3356+
mapper_: Mapper[_T1, Union["Future[_T2]", Observable[_T2]]] = mapper or cast(
3357+
Mapper[_T1, Union["Future[_T2]", Observable[_T2]]], of
3358+
)
3359+
3360+
return compose(
3361+
map(mapper_),
3362+
switch_latest(),
3363+
)
3364+
3365+
3366+
def switch_map_indexed(
3367+
mapper_indexed: Optional[MapperIndexed[_T1, Observable[_T2]]] = None
3368+
) -> Callable[[Observable[_T1]], Observable[_T2]]:
3369+
"""
3370+
The switch_map_indexed operator.
3371+
3372+
Project each element of an observable sequence into a new observable
3373+
by incorporating the element's index.
3374+
3375+
.. marble::
3376+
:alt: switch_map_indexed
3377+
3378+
---1-----------2-----------3----------->
3379+
[ switch_map_indexed(i,id: of(i, i ** 2, i + id)) ]
3380+
---1---1---1---2---4---3---3---9---5--->
3381+
3382+
Example:
3383+
>>> switch_map_indexed(lambda value, index: of(value, value // 2))
3384+
3385+
Args:
3386+
mapper_indexed: A transform function to apply to each source
3387+
element. The second parameter of the function represents
3388+
the index of the source element.
3389+
3390+
Returns:
3391+
A partially applied operator function that takes an observable
3392+
source and returns an observable sequence whose elements are
3393+
each element of the result of invoking the transform function
3394+
on each element of the source.
3395+
"""
3396+
3397+
def _of(value: _T1, _: int) -> Observable[_T2]:
3398+
return of(cast(_T2, value))
3399+
3400+
_mapper_indexed = mapper_indexed or cast(MapperIndexed[_T1, Observable[_T2]], _of)
3401+
3402+
return compose(
3403+
zip_with_iterable(infinite()),
3404+
switch_starmap_indexed(_mapper_indexed),
3405+
)
3406+
3407+
3408+
@overload
3409+
def switch_starmap(
3410+
mapper: Callable[[_A, _B], Observable[_T]]
3411+
) -> Callable[[Observable[Tuple[_A, _B]]], Observable[_T]]:
3412+
...
3413+
3414+
3415+
@overload
3416+
def switch_starmap(
3417+
mapper: Callable[[_A, _B, _C], Observable[_T]]
3418+
) -> Callable[[Observable[Tuple[_A, _B, _C]]], Observable[_T]]:
3419+
...
3420+
3421+
3422+
@overload
3423+
def switch_starmap(
3424+
mapper: Callable[[_A, _B, _C, _D], Observable[_T]]
3425+
) -> Callable[[Observable[Tuple[_A, _B, _C, _D]]], Observable[_T]]:
3426+
...
3427+
3428+
3429+
def switch_starmap(
3430+
mapper: Optional[Callable[..., Observable[Any]]] = None
3431+
) -> Callable[[Observable[Any]], Observable[Any]]:
3432+
"""The switch_starmap operator.
3433+
3434+
Unpack arguments grouped as tuple elements of an observable sequence
3435+
and return an observable sequence whose values are each element of
3436+
the observable returned by invoking the mapper function with star
3437+
applied on unpacked elements as positional arguments.
3438+
3439+
Use instead of `switch_map()` when the the arguments to the mapper is
3440+
grouped as tuples and the mapper function takes multiple arguments.
3441+
3442+
.. marble::
3443+
:alt: switch_starmap
3444+
3445+
----1,2-------3,4---------|
3446+
[ switch_starmap(of) ]
3447+
----1----2----3----4------|
3448+
3449+
Example:
3450+
>>> switch_starmap(lambda x, y: of(x + y, x * y))
3451+
3452+
Args:
3453+
mapper: A transform function to invoke with unpacked elements
3454+
as arguments.
3455+
3456+
Returns:
3457+
An operator function that takes an observable source and returns
3458+
an observable sequence whose values are each element of the
3459+
observable returned by invoking the mapper function with the
3460+
unpacked elements of the source.
3461+
"""
3462+
3463+
if mapper is None:
3464+
mapper = of
3465+
3466+
def starred(values: Tuple[Any, ...]) -> Observable[Any]:
3467+
return mapper(*values)
3468+
3469+
return compose(switch_map(starred))
3470+
3471+
3472+
@overload
3473+
def switch_starmap_indexed(
3474+
mapper: Callable[[_A, int], Observable[_T]]
3475+
) -> Callable[[Observable[_A]], Observable[_T]]:
3476+
...
3477+
3478+
3479+
@overload
3480+
def switch_starmap_indexed(
3481+
mapper: Callable[[_A, _B, int], Observable[_T]]
3482+
) -> Callable[[Observable[Tuple[_A, _B]]], Observable[_T]]:
3483+
...
3484+
3485+
3486+
@overload
3487+
def switch_starmap_indexed(
3488+
mapper: Callable[[_A, _B, _C, int], Observable[_T]]
3489+
) -> Callable[[Observable[Tuple[_A, _B, _C]]], Observable[_T]]:
3490+
...
3491+
3492+
3493+
@overload
3494+
def switch_starmap_indexed(
3495+
mapper: Callable[[_A, _B, _C, _D, int], Observable[_T]]
3496+
) -> Callable[[Observable[Tuple[_A, _B, _C, _D]]], Observable[_T]]:
3497+
...
3498+
3499+
3500+
def switch_starmap_indexed(
3501+
mapper: Optional[Callable[..., Observable[Any]]] = None
3502+
) -> Callable[[Observable[Any]], Observable[Any]]:
3503+
"""Variant of :func:`switch_starmap` which accepts an indexed mapper.
3504+
3505+
.. marble::
3506+
:alt: switch_starmap_indexed
3507+
3508+
------1,2----------3,4-----------|
3509+
[ switch_starmap_indexed(of) ]
3510+
------1---2---0----3---4---1-----|
3511+
3512+
Example:
3513+
>>> switch_starmap_indexed(lambda x, y, i: of(x + y + i, x * y - i))
3514+
3515+
Args:
3516+
mapper: A transform function to invoke with unpacked elements
3517+
as arguments.
3518+
3519+
Returns:
3520+
An operator function that takes an observable source and returns
3521+
an observable sequence whose values are each element of the
3522+
observable returned by invoking the mapper function with the
3523+
unpacked elements of the source.
3524+
"""
3525+
if mapper is None:
3526+
return compose(of)
3527+
3528+
def starred(values: Tuple[Any, ...]) -> Observable[Any]:
3529+
assert mapper # mypy is paranoid
3530+
return mapper(*values)
3531+
3532+
return compose(switch_map(starred))
3533+
3534+
33283535
def take(count: int) -> Callable[[Observable[_T]], Observable[_T]]:
33293536
"""Returns a specified number of contiguous elements from the start
33303537
of an observable sequence.
@@ -4272,6 +4479,10 @@ def zip_with_iterable(
42724479
"subscribe_on",
42734480
"sum",
42744481
"switch_latest",
4482+
"switch_map",
4483+
"switch_map_indexed",
4484+
"switch_starmap",
4485+
"switch_starmap_indexed",
42754486
"take",
42764487
"take_last",
42774488
"take_last_buffer",

tests/test_observable/test_map.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def _raise(ex):
2525
raise RxException(ex)
2626

2727

28-
class TestSelect(unittest.TestCase):
28+
class TestMap(unittest.TestCase):
2929
def test_map_throws(self):
3030
mapper = map(lambda x: x)
3131
with self.assertRaises(RxException):

tests/test_observable/test_starmap.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def _raise(ex):
2626
raise RxException(ex)
2727

2828

29-
class TestSelect(unittest.TestCase):
29+
class TestStarmap(unittest.TestCase):
3030
def test_starmap_never(self):
3131
scheduler = TestScheduler()
3232
xs = scheduler.create_hot_observable()

0 commit comments

Comments
 (0)