From 14979d43092cbaff068d49b65e5b4e8c3aabebd0 Mon Sep 17 00:00:00 2001 From: zeroswan Date: Wed, 18 Aug 2021 14:07:55 +0800 Subject: [PATCH 1/5] add type hinting --- functional/pipeline.py | 532 +++++++++++++++++++++++++++++++++++------ functional/streams.py | 25 +- 2 files changed, 488 insertions(+), 69 deletions(-) diff --git a/functional/pipeline.py b/functional/pipeline.py index 6e9a021..2117a50 100644 --- a/functional/pipeline.py +++ b/functional/pipeline.py @@ -9,6 +9,7 @@ import csv import sqlite3 import re +from typing_extensions import Literal from tabulate import tabulate @@ -24,15 +25,62 @@ from functional.io import WRITE_MODE, universal_write_open from functional import transformations from functional.execution import ExecutionStrategies +from typing import ( + Any, + Callable, + DefaultDict, + Dict, + Generic, + Iterable, + Iterator, + List, + NamedTuple, + NoReturn, + Optional, + Set, + Tuple, + TypeVar, + Union, + cast, + overload, +) + + +_T_co = TypeVar("_T_co", covariant=True) +_T1_co = TypeVar("_T1_co", covariant=True) +_T0 = TypeVar("_T0", bound="Sequence") +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") +_T4 = TypeVar("_T4") +_T5 = TypeVar("_T5") + + +_U = TypeVar("_U") +_U1 = TypeVar("_U1") +_K = TypeVar("_K") +_V = TypeVar("_V") +_PT = TypeVar("_PT", str, bool, float, complex, bytes, int, dict, set, NamedTuple) -class Sequence(object): + +class Sequence(Generic[_T_co]): """ Sequence is a wrapper around any type of sequence which provides access to common functional transformations and reductions in a data pipeline style """ - def __init__(self, sequence, transform=None, engine=None, max_repr_items=None): + _base_sequence: Iterable[_T_co] + _max_repr_items: Optional[int] + _lineage: Lineage + + def __init__( + self, + sequence: Iterable[_T_co], + transform=None, + engine=None, + max_repr_items=None, + ): # pylint: disable=protected-access """ Takes a Sequence, list, tuple. or iterable sequence and wraps it around a Sequence object. @@ -59,7 +107,7 @@ def __init__(self, sequence, transform=None, engine=None, max_repr_items=None): if transform is not None: self._lineage.apply(transform) - def __iter__(self): + def __iter__(self) -> Iterator[_T_co]: """ Return iterator of sequence. @@ -67,7 +115,7 @@ def __iter__(self): """ return self._evaluate() - def __eq__(self, other): + def __eq__(self, other) -> bool: """ Checks for equality with the sequence's equality operator. @@ -76,7 +124,7 @@ def __eq__(self, other): """ return self.sequence == other - def __ne__(self, other): + def __ne__(self, other) -> bool: """ Checks for inequality with the sequence's inequality operator. @@ -139,7 +187,7 @@ def __getitem__(self, item): self.cache() return _wrap(self.sequence[item]) - def __reversed__(self): + def __reversed__(self: _T0) -> _T0: """ Return reversed sequence using sequence's reverse function @@ -147,7 +195,7 @@ def __reversed__(self): """ return self._transform(transformations.reversed_t()) - def __contains__(self, item): + def __contains__(self, item) -> bool: """ Checks if item is in sequence. @@ -156,7 +204,7 @@ def __contains__(self, item): """ return self.sequence.__contains__(item) - def __add__(self, other): + def __add__(self, other: "Sequence[_T_co]") -> "Sequence[_T_co]": """ Concatenates sequence with other. @@ -168,7 +216,7 @@ def __add__(self, other): else: return Sequence(self.sequence + other) - def _evaluate(self): + def _evaluate(self) -> Iterable[_T_co]: """ Creates and returns an iterator which applies all the transformations in the lineage @@ -191,7 +239,7 @@ def _transform(self, *transforms): return sequence @property - def sequence(self): + def sequence(self) -> List[_T_co]: """ Alias for to_list used internally for brevity @@ -199,7 +247,7 @@ def sequence(self): """ return self.to_list() - def cache(self, delete_lineage=False): + def cache(self, delete_lineage=False) -> "Sequence[_T_co]": """ Caches the result of the Sequence so far. This means that any functions applied on the pipeline before cache() are evaluated, and the result is stored in the Sequence. This is @@ -220,7 +268,7 @@ def cache(self, delete_lineage=False): self._lineage = Lineage(engine=self.engine) return self - def head(self): + def head(self) -> _T_co: """ Returns the first element of the sequence. @@ -238,7 +286,7 @@ def head(self): """ return _wrap(self.take(1)[0]) - def first(self): + def first(self) -> _T_co: """ Returns the first element of the sequence. @@ -256,6 +304,18 @@ def first(self): """ return self.head() + @overload + def head_option(self: "Sequence[_PT]") -> Optional[_PT]: + ... + + @overload + def head_option(self: "Sequence[Iterable[_T1]]") -> Optional["Sequence[_T1]"]: + ... + + @overload + def head_option(self: "Sequence[_T2]") -> Optional[_T2]: + ... + def head_option(self): """ Returns the first element of the sequence or None, if the sequence is empty. @@ -272,7 +332,7 @@ def head_option(self): return None return self.head() - def last(self): + def last(self) -> _T_co: """ Returns the last element of the sequence. @@ -290,7 +350,7 @@ def last(self): """ return _wrap(self.sequence[-1]) - def last_option(self): + def last_option(self) -> Optional[_T_co]: """ Returns the last element of the sequence or None, if the sequence is empty. @@ -306,7 +366,7 @@ def last_option(self): return None return self.last() - def init(self): + def init(self) -> "Sequence[_T_co]": """ Returns the sequence, without its last element. @@ -317,7 +377,7 @@ def init(self): """ return self._transform(transformations.init_t()) - def tail(self): + def tail(self) -> "Sequence[_T_co]": """ Returns the sequence, without its first element. @@ -328,7 +388,7 @@ def tail(self): """ return self._transform(transformations.tail_t()) - def inits(self): + def inits(self) -> "Sequence[Sequence[_T_co]]": """ Returns consecutive inits of the sequence. @@ -339,7 +399,7 @@ def inits(self): """ return self._transform(transformations.inits_t(_wrap)) - def tails(self): + def tails(self) -> "Sequence[Sequence[_T_co]]": """ Returns consecutive tails of the sequence. @@ -350,6 +410,54 @@ def tails(self): """ return self._transform(transformations.tails_t(_wrap)) + @overload + def cartesian( + self, __iter1: Iterable[_T1], *, repeat: int = 1 + ) -> "Sequence[Tuple[_T_co, _T1]]": + ... + + @overload + def cartesian( + self, __iter1: Iterable[_T1], __iter2: Iterable[_T2], *, repeat: int = 1 + ) -> "Sequence[Tuple[_T_co, _T1, _T2]]": + ... + + @overload + def cartesian( + self, + __iter1: Iterable[_T1], + __iter2: Iterable[_T2], + __iter3: Iterable[_T3], + *, + repeat: int = 1 + ) -> "Sequence[Tuple[_T_co, _T1, _T2, _T3]]": + ... + + @overload + def cartesian( + self, + __iter1: Iterable[_T1], + __iter2: Iterable[_T2], + __iter3: Iterable[_T3], + __iter4: Iterable[_T4], + *, + repeat: int = 1 + ) -> "Sequence[Tuple[_T_co, _T1, _T2, _T3, _T4]]": + ... + + @overload + def cartesian( + self, + __iter1: Iterable[Any], + __iter2: Iterable[Any], + __iter3: Iterable[Any], + __iter4: Iterable[Any], + __iter5: Iterable[Any], + *__iters: Iterable[Any], + repeat: int = 1 + ) -> "Sequence[Tuple[Any, ...]]": + ... + def cartesian(self, *iterables, **kwargs): """ Returns the cartesian product of the passed iterables with the specified number of @@ -368,7 +476,7 @@ def cartesian(self, *iterables, **kwargs): transformations.cartesian_t(iterables, kwargs.get("repeat", 1)) ) - def drop(self, n): + def drop(self, n: int) -> "Sequence[_T_co]": """ Drop the first n elements of the sequence. @@ -383,7 +491,7 @@ def drop(self, n): else: return self._transform(transformations.drop_t(n)) - def drop_right(self, n): + def drop_right(self, n: int) -> "Sequence[_T_co]": """ Drops the last n elements of the sequence. @@ -395,7 +503,7 @@ def drop_right(self, n): """ return self._transform(transformations.drop_right_t(n)) - def drop_while(self, func): + def drop_while(self, func: Callable[[_T_co], bool]) -> "Sequence[_T_co]": """ Drops elements in the sequence while func evaluates to True, then returns the rest. @@ -407,7 +515,7 @@ def drop_while(self, func): """ return self._transform(transformations.drop_while_t(func)) - def take(self, n): + def take(self, n: int) -> "Sequence[_T_co]": """ Take the first n elements of the sequence. @@ -422,7 +530,7 @@ def take(self, n): else: return self._transform(transformations.take_t(n)) - def take_while(self, func): + def take_while(self, func: Callable[[_T_co], bool]) -> "Sequence[_T_co]": """ Take elements in the sequence until func evaluates to False, then return them. @@ -434,7 +542,7 @@ def take_while(self, func): """ return self._transform(transformations.take_while_t(func)) - def union(self, other): + def union(self, other: "Sequence[_T_co]") -> "Sequence[_T_co]": """ New sequence with unique elements from self and other. @@ -446,7 +554,7 @@ def union(self, other): """ return self._transform(transformations.union_t(other)) - def intersection(self, other): + def intersection(self, other: "Sequence[_T_co]") -> "Sequence[_T_co]": """ New sequence with unique elements present in sequence and other. @@ -458,7 +566,7 @@ def intersection(self, other): """ return self._transform(transformations.intersection_t(other)) - def difference(self, other): + def difference(self, other: "Sequence[_T_co]") -> "Sequence[_T_co]": """ New sequence with unique elements present in sequence but not in other. @@ -470,7 +578,7 @@ def difference(self, other): """ return self._transform(transformations.difference_t(other)) - def symmetric_difference(self, other): + def symmetric_difference(self, other: "Sequence[_T_co]") -> "Sequence[_T_co]": """ New sequence with elements in either sequence or other, but not both. @@ -482,7 +590,7 @@ def symmetric_difference(self, other): """ return self._transform(transformations.symmetric_difference_t(other)) - def map(self, func): + def map(self, func: Callable[[_T_co], _U]) -> "Sequence[_U]": """ Maps f onto the elements of the sequence. @@ -494,7 +602,7 @@ def map(self, func): """ return self._transform(transformations.map_t(func)) - def select(self, func): + def select(self, func: Callable[[_T_co], _U]) -> "Sequence[_U]": """ Selects f from the elements of the sequence. @@ -506,6 +614,42 @@ def select(self, func): """ return self._transform(transformations.select_t(func)) + @overload + def starmap( + self: "Sequence[Tuple[_T1]]", func: Callable[[_T1], _U] + ) -> "Sequence[_U]": + ... + + @overload + def starmap( + self: "Sequence[Tuple[_T1, _T2]]", func: Callable[[_T1, _T2], _U] + ) -> "Sequence[_U]": + ... + + @overload + def starmap( + self: "Sequence[Tuple[_T1, _T2, _T3]]", func: Callable[[_T1, _T2, _T3], _U] + ) -> "Sequence[_U]": + ... + + @overload + def starmap( + self: "Sequence[Tuple[_T1, _T2, _T3, _T4]]", + func: Callable[[_T1, _T2, _T3, _T4], _U], + ) -> "Sequence[_U]": + ... + + @overload + def starmap( + self: "Sequence[Tuple[_T1, _T2, _T3, _T4, _T5]]", + func: Callable[[_T1, _T2, _T3, _T4, _T5], _U], + ) -> "Sequence[_U]": + ... + + @overload + def starmap(self, func: Callable[..., _U]) -> "Sequence[_U]": + ... + def starmap(self, func): """ starmaps f onto the sequence as itertools.starmap does. @@ -532,7 +676,7 @@ def smap(self, func): """ return self._transform(transformations.starmap_t(func)) - def for_each(self, func): + def for_each(self, func: Callable[[_T_co], Any]) -> None: """ Executes func on each element of the sequence. @@ -546,7 +690,7 @@ def for_each(self, func): for e in self: func(e) - def filter(self, func): + def filter(self, func: Callable[[_T_co], bool]) -> "Sequence[_T_co]": """ Filters sequence to include only elements where func is True. @@ -558,7 +702,7 @@ def filter(self, func): """ return self._transform(transformations.filter_t(func)) - def filter_not(self, func): + def filter_not(self, func: Callable[[_T_co], bool]) -> "Sequence[_T_co]": """ Filters sequence to include only elements where func is False. @@ -570,7 +714,7 @@ def filter_not(self, func): """ return self._transform(transformations.filter_not_t(func)) - def where(self, func): + def where(self, func: Callable[[_T_co], bool]) -> "Sequence[_T_co]": """ Selects elements where func evaluates to True. @@ -582,7 +726,7 @@ def where(self, func): """ return self._transform(transformations.where_t(func)) - def count(self, func): + def count(self, func: Callable[[_T_co], bool]) -> int: """ Counts the number of elements in the sequence which satisfy the predicate func. @@ -598,7 +742,7 @@ def count(self, func): n += 1 return n - def len(self): + def len(self) -> int: """ Return length of sequence using its length function. @@ -674,7 +818,7 @@ def all(self): """ return all(self) - def exists(self, func): + def exists(self, func: Callable[[_T_co], bool]): """ Returns True if an element in the sequence makes func evaluate to True. @@ -692,7 +836,7 @@ def exists(self, func): return True return False - def for_all(self, func): + def for_all(self, func: Callable[[_T_co], bool]): """ Returns True if all elements in sequence make func evaluate to True. @@ -710,6 +854,14 @@ def for_all(self, func): return False return True + @overload + def max(self: "Sequence[Iterable[_T1]]") -> "Sequence[_T1]": + ... + + @overload + def max(self) -> _T_co: + ... + def max(self): """ Returns the largest element in the sequence. @@ -741,6 +893,14 @@ def max(self): """ return _wrap(max(self)) + @overload + def min(self: "Sequence[Iterable[_T1]]") -> "Sequence[_T1]": + ... + + @overload + def min(self) -> _T_co: + ... + def min(self): """ Returns the smallest element in the sequence. @@ -772,6 +932,16 @@ def min(self): """ return _wrap(min(self)) + @overload + def max_by( + self: "Sequence[Iterable[_T1]]", func: Callable[[Iterable[_T1]], _U] + ) -> "Sequence[_T1]": + ... + + @overload + def max_by(self, func: Callable[[_T_co], _U]) -> _T_co: + ... + def max_by(self, func): """ Returns the largest element in the sequence. @@ -797,6 +967,16 @@ def max_by(self, func): """ return _wrap(max(self, key=func)) + @overload + def min_by( + self: "Sequence[Iterable[_T1]]", func: Callable[[Iterable[_T1]], _U] + ) -> "Sequence[_T1]": + ... + + @overload + def min_by(self, func: Callable[[_T_co], _U]) -> _T_co: + ... + def min_by(self, func): """ Returns the smallest element in the sequence. @@ -822,7 +1002,7 @@ def min_by(self, func): """ return _wrap(min(self, key=func)) - def find(self, func): + def find(self, func: Callable[[_T_co], bool]) -> _T_co: """ Finds the first element of the sequence that satisfies func. If no such element exists, then return None. @@ -838,7 +1018,7 @@ def find(self, func): return element return None - def flatten(self): + def flatten(self: "Sequence[Iterable[_T1]]") -> "Sequence[_T1]": """ Flattens a sequence of sequences to a single sequence of elements. @@ -849,7 +1029,7 @@ def flatten(self): """ return self._transform(transformations.flatten_t()) - def flat_map(self, func): + def flat_map(self, func: Callable[[_T_co], Iterable[_U]]) -> "Sequence[_U]": """ Applies func to each element of the sequence, which themselves should be sequences. Then appends each element of each sequence to a final result @@ -868,7 +1048,9 @@ def flat_map(self, func): """ return self._transform(transformations.flat_map_t(func)) - def group_by(self, func): + def group_by( + self, func: Callable[[_T_co], _U] + ) -> "Sequence[Tuple[_U, List[_T_co]]]": """ Group elements into a list of (Key, Value) tuples where func creates the key and maps to values matching that key. @@ -881,7 +1063,9 @@ def group_by(self, func): """ return self._transform(transformations.group_by_t(func)) - def group_by_key(self): + def group_by_key( + self: "Sequence[Tuple[_K, _V]]", + ) -> "Sequence[Tuple[_K, List[_V]]]": """ Group sequence of (Key, Value) elements by Key. @@ -892,7 +1076,9 @@ def group_by_key(self): """ return self._transform(transformations.group_by_key_t()) - def reduce_by_key(self, func): + def reduce_by_key( + self: "Sequence[Tuple[_K, _V]]", func: Callable[[_K, _V], _V] + ) -> "Sequence[Tuple[_K, _V]]": """ Reduces a sequence of (Key, Value) using func on each sequence of values. @@ -905,7 +1091,7 @@ def reduce_by_key(self, func): """ return self._transform(transformations.reduce_by_key_t(func)) - def count_by_key(self): + def count_by_key(self: "Sequence[Tuple[_K, _V]]") -> "Sequence[Tuple[_K, int]]": """ Reduces a sequence of (Key, Value) by counting each key @@ -915,7 +1101,7 @@ def count_by_key(self): """ return self._transform(transformations.count_by_key_t()) - def count_by_value(self): + def count_by_value(self) -> "Sequence[Tuple[_T_co, int]]": """ Reduces a sequence of items by counting each unique item @@ -925,6 +1111,14 @@ def count_by_value(self): """ return self._transform(transformations.count_by_value_t()) + @overload + def reduce(self, func: Callable[[_T_co, _T_co], _T_co]) -> _T_co: + ... + + @overload + def reduce(self, func: Callable[[_U, _T_co], _U], *, initial: _U) -> _U: + ... + def reduce(self, func, *initial): """ Reduce sequence of elements using func. API mirrors functools.reduce @@ -945,7 +1139,7 @@ def reduce(self, func, *initial): "reduce takes exactly one optional parameter for initial value" ) - def accumulate(self, func=add): + def accumulate(self, func: Callable[[_U, _T_co], _U] = add) -> "Sequence[_U]": """ Accumulate sequence of elements using func. API mirrors itertools.accumulate @@ -960,7 +1154,7 @@ def accumulate(self, func=add): """ return self._transform(transformations.accumulate_t(func)) - def make_string(self, separator): + def make_string(self, separator: str) -> str: """ Concatenate the elements of the sequence into a string separated by separator. @@ -972,6 +1166,30 @@ def make_string(self, separator): """ return separator.join(str(e) for e in self) + @overload + def product(self, projection: Callable[[_T_co], _PT]) -> _PT: + ... + + @overload + def product(self, projection: Callable[[_T_co], Iterable[_U]]) -> "Sequence[_U]": + ... + + @overload + def product(self, projection: Callable[[_T_co], _U]) -> _U: + ... + + @overload + def product(self: "Sequence[_PT]") -> _PT: + ... + + @overload + def product(self: "Sequence[Iterable[_T1]]") -> "Sequence[_T1]": + ... + + @overload + def product(self) -> _T_co: + ... + def product(self, projection=None): """ Takes product of elements in sequence. @@ -1004,6 +1222,30 @@ def product(self, projection=None): else: return self.reduce(mul) + @overload + def sum(self, projection: Callable[[_T_co], _PT]) -> _PT: + ... + + @overload + def sum(self, projection: Callable[[_T_co], Iterable[_U]]) -> "Sequence[_U]": + ... + + @overload + def sum(self, projection: Callable[[_T_co], _U]) -> _U: + ... + + @overload + def sum(self: "Sequence[_PT]") -> _PT: + ... + + @overload + def sum(self: "Sequence[Iterable[_T1]]") -> "Sequence[_T1]": + ... + + @overload + def sum(self) -> _T_co: + ... + def sum(self, projection=None): """ Takes sum of elements in sequence. @@ -1022,6 +1264,30 @@ def sum(self, projection=None): else: return sum(self) + @overload + def average(self, projection: Callable[[_T_co], _PT]) -> _PT: + ... + + @overload + def average(self, projection: Callable[[_T_co], Iterable[_U]]) -> "Sequence[_U]": + ... + + @overload + def average(self, projection: Callable[[_T_co], _U]) -> _U: + ... + + @overload + def average(self: "Sequence[_PT]") -> _PT: + ... + + @overload + def average(self: "Sequence[Iterable[_T1]]") -> "Sequence[_T1]": + ... + + @overload + def average(self) -> _T_co: + ... + def average(self, projection=None): """ Takes the average of elements in the sequence @@ -1040,6 +1306,27 @@ def average(self, projection=None): else: return sum(self) / length + @overload + def aggregate( + self, __func: Callable[[_T_co, _T_co], _T_co] + ) -> Union[_T_co, "Sequence"]: + ... + + @overload + def aggregate( + self, __seed: _U, __func: Callable[[_T_co, _U], _U] + ) -> Union[_U, "Sequence"]: + ... + + @overload + def aggregate( + self, + __seed: _U, + __func: Callable[[_T_co, _U], _U], + __result_lambda: Callable[[_U], _U1], + ) -> Union[_U, "Sequence"]: + ... + def aggregate(self, *args): """ Aggregates the sequence by specified arguments. Its behavior varies depending on if one, @@ -1079,6 +1366,22 @@ def aggregate(self, *args): else: return result_lambda(self.fold_left(seed, func)) + @overload + def fold_left(self, zero_value: _PT, func: Callable[[_T_co, _PT], _PT]) -> _PT: + ... + + @overload + def fold_left( + self, + zero_value: Iterable[_U], + func: Callable[[_T_co, Iterable[_U]], Iterable[_U]], + ) -> "Sequence[_U]": + ... + + @overload + def fold_left(self, zero_value: _U, func: Callable[[_T_co, _U], _U]) -> _U: + ... + def fold_left(self, zero_value, func): """ Assuming that the sequence elements are of type A, folds from left to right starting with @@ -1098,6 +1401,22 @@ def fold_left(self, zero_value, func): result = func(result, element) return _wrap(result) + @overload + def fold_right(self, zero_value: _PT, func: Callable[[_T_co, _PT], _PT]) -> _PT: + ... + + @overload + def fold_right( + self, + zero_value: Iterable[_U], + func: Callable[[_T_co, Iterable[_U]], Iterable[_U]], + ) -> "Sequence[_U]": + ... + + @overload + def fold_right(self, zero_value: _U, func: Callable[[_T_co, _U], _U]) -> _U: + ... + def fold_right(self, zero_value, func): """ Assuming that the sequence elements are of type A, folds from right to left starting with @@ -1117,7 +1436,7 @@ def fold_right(self, zero_value, func): result = func(element, result) return _wrap(result) - def zip(self, sequence): + def zip(self, sequence: "Sequence[_T1_co]") -> "Sequence[Tuple[_T_co, _T1_co]]": """ Zips the stored sequence with the given sequence. @@ -1129,7 +1448,7 @@ def zip(self, sequence): """ return self._transform(transformations.zip_t(sequence)) - def zip_with_index(self, start=0): + def zip_with_index(self, start: int = 0) -> "Sequence[Tuple[_T_co, int]]": """ Zips the sequence to its index, with the index being the second element of each tuple. @@ -1140,7 +1459,7 @@ def zip_with_index(self, start=0): """ return self._transform(transformations.zip_with_index_t(start)) - def enumerate(self, start=0): + def enumerate(self, start: int = 0) -> "Sequence[Tuple[int, _T_co]]": """ Uses python enumerate to to zip the sequence with indexes starting at start. @@ -1152,7 +1471,7 @@ def enumerate(self, start=0): """ return self._transform(transformations.enumerate_t(start)) - def inner_join(self, other): + def inner_join(self: "Sequence[Tuple[_K, _V]]", other: "Sequence[Tuple[_K, _V]]"): """ Sequence and other must be composed of (Key, Value) pairs. If self.sequence contains (K, V) pairs and other contains (K, W) pairs, the return result @@ -1167,6 +1486,38 @@ def inner_join(self, other): """ return self.join(other, "inner") + @overload + def join( + self: "Sequence[Tuple[_T1, _T2]]", + other: "Sequence[Tuple[_T1, _T2]]", + join_type: Literal["inner"], + ) -> "Sequence[Tuple[_T1, Tuple[_T2, _T2]]]": + ... + + @overload + def join( + self: "Sequence[Tuple[_T1, _T2]]", + other: "Sequence[Tuple[_T1, _T2]]", + join_type: Literal["outer"], + ) -> "Sequence[Tuple[_T1, Tuple[Optional[_T2], Optional[_T2]]]]": + ... + + @overload + def join( + self: "Sequence[Tuple[_T1, _T2]]", + other: "Sequence[Tuple[_T1, _T2]]", + join_type: Literal["left"], + ) -> "Sequence[Tuple[_T1, Tuple[Optional[_T2], _T2]]]": + ... + + @overload + def join( + self: "Sequence[Tuple[_T1, _T2]]", + other: "Sequence[Tuple[_T1, _T2]]", + join_type: Literal["right"], + ) -> "Sequence[Tuple[_T1, Tuple[_T2, Optional[_T2]]]]": + ... + def join(self, other, join_type="inner"): """ Sequence and other must be composed of (Key, Value) pairs. If self.sequence contains (K, V) @@ -1197,7 +1548,7 @@ def join(self, other, join_type="inner"): """ return self._transform(transformations.join_t(other, join_type)) - def left_join(self, other): + def left_join(self: "Sequence[Tuple[_K, _V]]", other: "Sequence[Tuple[_K, _V]]"): """ Sequence and other must be composed of (Key, Value) pairs. If self.sequence contains (K, V) pairs and other contains (K, W) pairs, the return result is a sequence of (K, (V, W)) pairs. @@ -1211,7 +1562,7 @@ def left_join(self, other): """ return self.join(other, "left") - def right_join(self, other): + def right_join(self: "Sequence[Tuple[_K, _V]]", other: "Sequence[Tuple[_K, _V]]"): """ Sequence and other must be composed of (Key, Value) pairs. If self.sequence contains (K, V) pairs and other contains (K, W) pairs, the return result is a sequence of (K, (V, W)) pairs. @@ -1225,7 +1576,7 @@ def right_join(self, other): """ return self.join(other, "right") - def outer_join(self, other): + def outer_join(self: "Sequence[Tuple[_K, _V]]", other: "Sequence[Tuple[_K, _V]]"): """ Sequence and other must be composed of (Key, Value) pairs. If self.sequence contains (K, V) pairs and other contains (K, W) pairs, the return result is a sequence of (K, (V, W)) pairs. @@ -1239,7 +1590,7 @@ def outer_join(self, other): """ return self.join(other, "outer") - def partition(self, func): + def partition(self, func: Callable[[_T_co], bool]) -> "Sequence[Sequence[_T_co]]": """ Partition the sequence based on satisfying the predicate func. @@ -1251,7 +1602,7 @@ def partition(self, func): """ return self._transform(transformations.partition_t(_wrap, func)) - def grouped(self, size): + def grouped(self, size: int) -> "Sequence[List[_T_co]]": """ Partitions the elements into groups of length size. @@ -1268,7 +1619,7 @@ def grouped(self, size): """ return self._transform(transformations.grouped_t(size)) - def sliding(self, size, step=1): + def sliding(self, size: int, step=1) -> "Sequence[List[_T_co]]": """ Groups elements in fixed size blocks by passing a sliding window over them. @@ -1280,7 +1631,9 @@ def sliding(self, size, step=1): """ return self._transform(transformations.sliding_t(_wrap, size, step)) - def sorted(self, key=None, reverse=False): + def sorted( + self, key: Optional[Callable[[_T_co], Any]] = None, reverse: bool = False + ) -> "Sequence[_T_co]": """ Uses python sort and its passed arguments to sort the input. @@ -1293,7 +1646,7 @@ def sorted(self, key=None, reverse=False): """ return self._transform(transformations.sorted_t(key=key, reverse=reverse)) - def order_by(self, func): + def order_by(self, func: Optional[Callable[[_T_co], Any]]) -> "Sequence[_T_co]": """ Orders the input according to func @@ -1305,7 +1658,7 @@ def order_by(self, func): """ return self._transform(transformations.order_by_t(func)) - def reverse(self): + def reverse(self) -> "Sequence[_T_co]": """ Returns the reversed sequence. @@ -1316,7 +1669,7 @@ def reverse(self): """ return reversed(self) - def distinct(self): + def distinct(self) -> "Sequence[_T_co]": """ Returns sequence of distinct elements. Elements must be hashable. @@ -1327,7 +1680,7 @@ def distinct(self): """ return self._transform(transformations.distinct_t()) - def distinct_by(self, func): + def distinct_by(self, func: Callable[[_T_co], Any]) -> "Sequence[_T_co]": """ Returns sequence of elements who are distinct by the passed function. The return value of func must be hashable. When two elements are distinct by func, the first is taken. @@ -1337,7 +1690,7 @@ def distinct_by(self, func): """ return self._transform(transformations.distinct_by_t(func)) - def slice(self, start, until): + def slice(self, start: int, until: int) -> "Sequence[_T_co]": """ Takes a slice of the sequence starting at start and until but not including until. @@ -1352,7 +1705,7 @@ def slice(self, start, until): """ return self._transform(transformations.slice_t(start, until)) - def to_list(self, n=None): + def to_list(self, n: int = None) -> List[_T_co]: """ Converts sequence to list of elements. @@ -1374,7 +1727,7 @@ def to_list(self, n=None): else: return self.cache().take(n).list() - def list(self, n=None): + def list(self, n: int = None) -> List[_T_co]: """ Converts sequence to list of elements. @@ -1392,7 +1745,7 @@ def list(self, n=None): """ return self.to_list(n=n) - def to_set(self): + def to_set(self) -> Set[_T_co]: """ Converts sequence to a set of elements. @@ -1409,7 +1762,7 @@ def to_set(self): """ return set(self.sequence) - def set(self): + def set(self) -> Set[_T_co]: """ Converts sequence to a set of elements. @@ -1426,6 +1779,20 @@ def set(self): """ return self.to_set() + @overload + def to_dict(self: "Sequence[Tuple[_K, _V]]") -> Dict[_K, _V]: + ... + + @overload + def to_dict( + self: "Sequence[Tuple[_K, _V]]", default: Callable[[], _V] + ) -> DefaultDict[_K, _V]: + ... + + @overload + def to_dict(self: "Sequence[Tuple[_K, _V]]", default: _V) -> DefaultDict[_K, _V]: + ... + def to_dict(self, default=None): """ Converts sequence of (Key, Value) pairs to a dictionary. @@ -1453,6 +1820,20 @@ def to_dict(self, default=None): else: return collections.defaultdict(lambda: default, dictionary) + @overload + def dict(self: "Sequence[Tuple[_K, _V]]") -> Dict[_K, _V]: + ... + + @overload + def dict( + self: "Sequence[Tuple[_K, _V]]", default: Callable[[], _V] + ) -> DefaultDict[_K, _V]: + ... + + @overload + def dict(self: "Sequence[Tuple[_K, _V]]", default: _V) -> DefaultDict[_K, _V]: + ... + def dict(self, default=None): """ Converts sequence of (Key, Value) pairs to a dictionary. @@ -1775,6 +2156,21 @@ def tabulate( ) +@overload +def _wrap(value: _PT) -> _PT: + ... + + +@overload +def _wrap(value: Iterable[_T_co]) -> Sequence[_T_co]: + ... + + +@overload +def _wrap(value: _T_co) -> _T_co: + ... + + def _wrap(value): """ Wraps the passed value in a Sequence if it is not a primitive. If it is a string diff --git a/functional/streams.py b/functional/streams.py index afdd11b..baedd94 100644 --- a/functional/streams.py +++ b/functional/streams.py @@ -3,13 +3,18 @@ import json as jsonapi import sqlite3 as sqlite3api import builtins +from typing import Any, Iterable, TypeVar, overload from functional.execution import ExecutionEngine, ParallelExecutionEngine -from functional.pipeline import Sequence +from functional.pipeline import Sequence, _PT from functional.util import is_primitive from functional.io import get_read_function +_T_co = TypeVar("_T_co", covariant=True) +_T2_co = TypeVar("_T2_co", contravariant=True) + + class Stream(object): """ Represents and implements a stream which separates the responsibilities of Sequence and @@ -26,6 +31,24 @@ def __init__(self, disable_compression=False, max_repr_items=100): self.disable_compression = disable_compression self.max_repr_items = max_repr_items + @overload + def __call__(self) -> Sequence[Any]: + ... + + @overload + def __call__(self, __iter: Iterable[_T_co]) -> Sequence[_T_co]: + ... + + @overload + def __call__(self, __pt: _PT) -> Sequence[_PT]: + ... + + @overload + def __call__( + self, __item: _T2_co, __item2: _T2_co, *__args: Iterable[_T2_co] + ) -> Sequence[_T2_co]: + ... + def __call__(self, *args, **kwargs): """ Create a Sequence using a sequential ExecutionEngine. From f18f8e3e12f71917e00c3e8c5fb55078886fd616 Mon Sep 17 00:00:00 2001 From: zeroswan Date: Wed, 18 Aug 2021 20:38:36 +0800 Subject: [PATCH 2/5] add type hinting , bug fix --- functional/pipeline.py | 10 ++++++---- pyproject.toml | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/functional/pipeline.py b/functional/pipeline.py index 2117a50..913b623 100644 --- a/functional/pipeline.py +++ b/functional/pipeline.py @@ -187,7 +187,7 @@ def __getitem__(self, item): self.cache() return _wrap(self.sequence[item]) - def __reversed__(self: _T0) -> _T0: + def __reversed__(self) -> "Sequence[_T_co]": """ Return reversed sequence using sequence's reverse function @@ -204,7 +204,9 @@ def __contains__(self, item) -> bool: """ return self.sequence.__contains__(item) - def __add__(self, other: "Sequence[_T_co]") -> "Sequence[_T_co]": + def __add__( + self, other: Union[List[_T_co], "Sequence[_T_co]"] + ) -> "Sequence[_T_co]": """ Concatenates sequence with other. @@ -1002,7 +1004,7 @@ def min_by(self, func): """ return _wrap(min(self, key=func)) - def find(self, func: Callable[[_T_co], bool]) -> _T_co: + def find(self, func: Callable[[_T_co], bool]) -> Optional[_T_co]: """ Finds the first element of the sequence that satisfies func. If no such element exists, then return None. @@ -1658,7 +1660,7 @@ def order_by(self, func: Optional[Callable[[_T_co], Any]]) -> "Sequence[_T_co]": """ return self._transform(transformations.order_by_t(func)) - def reverse(self) -> "Sequence[_T_co]": + def reverse(self): """ Returns the reversed sequence. diff --git a/pyproject.toml b/pyproject.toml index fb8037d..43a38fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ python = "^3.6,>=3.6.1" dill = ">=0.2.5" tabulate = "<=1.0.0" pandas = {version = "^1.0.3", optional = true} +typing-extensions = "^3.7" [tool.poetry.extras] all = ["pandas"] From e1107642965c72c155c85ca950484468f46c992d Mon Sep 17 00:00:00 2001 From: zeroswan Date: Wed, 18 Aug 2021 20:48:11 +0800 Subject: [PATCH 3/5] add type hinting , remove unused import --- functional/pipeline.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/functional/pipeline.py b/functional/pipeline.py index 913b623..892d23e 100644 --- a/functional/pipeline.py +++ b/functional/pipeline.py @@ -35,20 +35,17 @@ Iterator, List, NamedTuple, - NoReturn, Optional, Set, Tuple, TypeVar, Union, - cast, overload, ) _T_co = TypeVar("_T_co", covariant=True) _T1_co = TypeVar("_T1_co", covariant=True) -_T0 = TypeVar("_T0", bound="Sequence") _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _T3 = TypeVar("_T3") @@ -218,7 +215,7 @@ def __add__( else: return Sequence(self.sequence + other) - def _evaluate(self) -> Iterable[_T_co]: + def _evaluate(self) -> Iterator[_T_co]: """ Creates and returns an iterator which applies all the transformations in the lineage From 1881e70c5c37c18a3407dd90ab4a33eb20b80bfe Mon Sep 17 00:00:00 2001 From: zeroswan Date: Wed, 18 Aug 2021 23:33:14 +0800 Subject: [PATCH 4/5] add typing, fix wrong-import-order --- functional/pipeline.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/functional/pipeline.py b/functional/pipeline.py index 892d23e..194a4d9 100644 --- a/functional/pipeline.py +++ b/functional/pipeline.py @@ -4,11 +4,29 @@ from operator import mul, add import collections from functools import reduce, wraps, partial +from typing import ( + Any, + Callable, + DefaultDict, + Dict, + Generic, + Iterable, + Iterator, + List, + NamedTuple, + Optional, + Set, + Tuple, + TypeVar, + Union, + overload, +) import json import csv import sqlite3 import re + from typing_extensions import Literal from tabulate import tabulate @@ -25,23 +43,6 @@ from functional.io import WRITE_MODE, universal_write_open from functional import transformations from functional.execution import ExecutionStrategies -from typing import ( - Any, - Callable, - DefaultDict, - Dict, - Generic, - Iterable, - Iterator, - List, - NamedTuple, - Optional, - Set, - Tuple, - TypeVar, - Union, - overload, -) _T_co = TypeVar("_T_co", covariant=True) From b8f555a9defe68a6f0b2fc87761f9bf36070bf22 Mon Sep 17 00:00:00 2001 From: zeroswan Date: Fri, 20 Aug 2021 08:43:05 +0800 Subject: [PATCH 5/5] =?UTF-8?q?modify=20coveragerc=20to=20ignore=20"..."?= =?UTF-8?q?=20lines,=20ignore=20pylint=20errors:=20TypeVar=20invalid-name?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .coveragerc | 6 +++++- functional/pipeline.py | 6 +++--- functional/streams.py | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.coveragerc b/.coveragerc index 40b3f80..b3b043b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,7 @@ # .coveragerc [report] -show_missing = True \ No newline at end of file +show_missing = True +exclude_lines = + pragma: no cover + # ignore lines contains only ellipsis + ^\s*\.\.\.\s*$ diff --git a/functional/pipeline.py b/functional/pipeline.py index 194a4d9..2709982 100644 --- a/functional/pipeline.py +++ b/functional/pipeline.py @@ -4,7 +4,7 @@ from operator import mul, add import collections from functools import reduce, wraps, partial -from typing import ( +from typing import ( # pylint: disable=unused-import Any, Callable, DefaultDict, @@ -45,8 +45,8 @@ from functional.execution import ExecutionStrategies -_T_co = TypeVar("_T_co", covariant=True) -_T1_co = TypeVar("_T1_co", covariant=True) +_T_co = TypeVar("_T_co", covariant=True) # pylint: disable=invalid-name +_T1_co = TypeVar("_T1_co", covariant=True) # pylint: disable=invalid-name _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _T3 = TypeVar("_T3") diff --git a/functional/streams.py b/functional/streams.py index baedd94..ed61616 100644 --- a/functional/streams.py +++ b/functional/streams.py @@ -11,8 +11,8 @@ from functional.io import get_read_function -_T_co = TypeVar("_T_co", covariant=True) -_T2_co = TypeVar("_T2_co", contravariant=True) +_T_co = TypeVar("_T_co", covariant=True) # pylint: disable=invalid-name +_T2_co = TypeVar("_T2_co", contravariant=True) # pylint: disable=invalid-name class Stream(object):