-
Notifications
You must be signed in to change notification settings - Fork 361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix type hints for pipe #355
Comments
I was thinking that when we chain operators, only the last one could have a different signature, exiting the Observable chain. What about adding an extra optional keyword parameter when one wants to transform the result into something else (list, future, etc.). Thus it should be easy to guaranty that only this last operator would be different. However, that would break the current API. def pipe(*operators: Callable[[Observable], Observable], tail: Callable[[Observable], T] = None,
) -> Union[Callable[[Observable], Observable], Callable[[Observable], T]]: Just below a 'working' prototype that seems to work in IDE & mypy. from typing import Callable, TypeVar, Union
from functools import reduce
class Observable():
def foo(self):
pass
# mimicks an operator that return an Observable
def obs_obs(obs: Observable) -> Observable:
return Observable()
# mimicks an operator that return a List
def obs_list(obs:Observable) -> list:
return [0, 1, 2]
T = TypeVar('T')
def pipe(*operators: Callable[[Observable], Observable], tail: Callable[[Observable], T] = None,
) -> Union[Callable[[Observable], Observable], Callable[[Observable], T]]:
def compose(source: Observable) -> Observable:
return reduce(lambda obs, op: op(obs), operators, source)
if tail:
return lambda s: tail(compose(s))
return compose
composition = pipe(obs_obs, obs_obs)
composition_to_list = pipe(obs_obs, obs_obs, tail=obs_list)
res_obs = composition(Observable())
res_list = composition_to_list(Observable()) EDIT: |
Actually I like your first suggestion and I would like to avoid having to use a separate |
I didn't thought about that. However, the creation operators return an EDIT: forget what I've said, it should be compatible with the So if we go this way, how should we name this keyword parameter? What about |
I also prefer @jcafhe solution rather than declaring many signatures. Alternatively, would it make sense to make these operators non pipable (since they do not follow the observable in -> observable out convention), and just expose them as regular functions ? for example: ''' |
A note to the impl. by @jcafhe is that you should probably not need the Here is my try on an overloaded version. Mypy seems to like the overloads, but pylint complains with @overload
def pipe() -> Callable[[A], A]:
... # pylint: disable=pointless-statement
@overload
def pipe(op1: Callable[[A], B]) -> Callable[[A], B]: # pylint: disable=function-redefined
... # pylint: disable=pointless-statement
@overload
def pipe(op1: Callable[[A], B], op2: Callable[[B], C]) -> Callable[[A], C]: # pylint: disable=function-redefined
... # pylint: disable=pointless-statement
@overload
def pipe(op1: Callable[[A], B], op2: Callable[[B], C], op3: Callable[[C], D]) -> Callable[[A], D]: # pylint: disable=function-redefined
... # pylint: disable=pointless-statement
@overload
def pipe(op1: Callable[[A], B], op2: Callable[[B], C], op3: Callable[[C], D], op4: Callable[[D], E]) -> Callable[[A], E]: # pylint: disable=function-redefined
... # pylint: disable=pointless-statement
# pylint: disable=function-redefined
def pipe(*operators: Callable[[Any], Any]) -> Callable[[Any], Any]: # type: ignore
... and for Observable: @overload
def pipe(self) -> 'Observable': # pylint: disable=no-self-use
... # pylint: disable=pointless-statement
@overload
def pipe(self, op1: Callable[['Observable'], A]) -> A: # pylint: disable=function-redefined, no-self-use
... # pylint: disable=pointless-statement
@overload
def pipe(self, op1: Callable[['Observable'], A], op2: Callable[[A], B]) -> B: # pylint: disable=function-redefined, no-self-use
... # pylint: disable=pointless-statement
@overload
def pipe(self, op1: Callable[['Observable'], A], op2: Callable[[A], B], op3: Callable[[B], C]) -> C: # pylint: disable=function-redefined, no-self-use
... # pylint: disable=pointless-statement
@overload
def pipe(self, op1: Callable[['Observable'], A], op2: Callable[[A], B], op3: Callable[[B], C], op4: Callable[[C], D]) -> D: # pylint: disable=function-redefined, no-self-use
... # pylint: disable=pointless-statement
# pylint: disable=function-redefined
def pipe(self, *operators: Callable[['Observable'], Any]) -> Any: # type: ignore
... |
Yes you are right. I'm a bit lost here, I was confused with the If it helps, here is a quick overview of operators in rx/operators with an 'exotic' return type:
I believe some operators are incorrectly typed too, but they should have a 'classical' rtype:
@MainRo that makes sense IMO. |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
The current type hints for the pipe operator is not correct since it assumes that the final type will always be an
Observable
. Thus there's problems when piping to functions such asto_future
that returns aFuture
. The current typing hints are:The correct would be that the callables were
Callable[[A], B]
andCallable[[B], C]
etc, but this is not possible when using the star operator as above. We would probably need to do something like using overloads and specifying the N first cases as with RxJS and typescript e.g:But I'm unsure how to do this properly in Python so that IDEs, mypy and linters acually can handle this so you get proper completion on the stuff that comes out of the pipe.
The text was updated successfully, but these errors were encountered: