From e7e456197877ee0d7173aacf2613462ce91ba236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Thu, 10 Jun 2021 10:30:06 -0400 Subject: [PATCH 1/7] TYP: annotate curry --- toolz/functoolz.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/toolz/functoolz.py b/toolz/functoolz.py index c568aae8..25e1fbe9 100644 --- a/toolz/functoolz.py +++ b/toolz/functoolz.py @@ -5,6 +5,7 @@ from importlib import import_module from textwrap import dedent from types import MethodType +from typing import Any, Callable, Generic, TypeVar from .utils import no_default @@ -14,6 +15,7 @@ 'curry', 'flip', 'excepts') PYPY = hasattr(sys, 'pypy_version_info') +_T = TypeVar("_T") def identity(x): @@ -164,7 +166,7 @@ def __reduce__(self): return InstanceProperty, state -class curry(object): +class curry(Generic[_T]): """ Curry a callable function Enables partial application of arguments through calling a function with an @@ -192,10 +194,7 @@ class curry(object): toolz.curried - namespace of curried functions https://toolz.readthedocs.io/en/latest/curry.html """ - def __init__(self, *args, **kwargs): - if not args: - raise TypeError('__init__() takes at least 2 arguments (1 given)') - func, args = args[0], args[1:] + def __init__(self, func: Callable[..., _T], *args: Any, **kwargs: Any) -> None: if not callable(func): raise TypeError("Input must be callable") @@ -298,7 +297,7 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) - def __call__(self, *args, **kwargs): + def __call__(self, *args: Any, **kwargs: Any) -> _T: try: return self._partial(*args, **kwargs) except TypeError as exc: From f95a764223e23bc046d63cb1573d4cb489637775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Thu, 10 Jun 2021 14:10:20 -0400 Subject: [PATCH 2/7] should fix name collision for python 3.8+ --- toolz/functoolz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolz/functoolz.py b/toolz/functoolz.py index 25e1fbe9..73939ad9 100644 --- a/toolz/functoolz.py +++ b/toolz/functoolz.py @@ -194,7 +194,7 @@ class curry(Generic[_T]): toolz.curried - namespace of curried functions https://toolz.readthedocs.io/en/latest/curry.html """ - def __init__(self, func: Callable[..., _T], *args: Any, **kwargs: Any) -> None: + def __init__(self, func: Callable[..., _T], /, *args: Any, **kwargs: Any) -> None: if not callable(func): raise TypeError("Input must be callable") From ec39da5237bc055a3bbaedf084b3e6187b56d606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Thu, 10 Jun 2021 14:22:55 -0400 Subject: [PATCH 3/7] __call__ can also return another curry object --- toolz/functoolz.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/toolz/functoolz.py b/toolz/functoolz.py index 73939ad9..b0135518 100644 --- a/toolz/functoolz.py +++ b/toolz/functoolz.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import reduce, partial import inspect import sys @@ -5,7 +7,7 @@ from importlib import import_module from textwrap import dedent from types import MethodType -from typing import Any, Callable, Generic, TypeVar +from typing import Any, Callable, Generic, TypeVar, Union from .utils import no_default @@ -297,7 +299,7 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) - def __call__(self, *args: Any, **kwargs: Any) -> _T: + def __call__(self, *args: Any, **kwargs: Any) -> Union[_T, curry[_T]]: try: return self._partial(*args, **kwargs) except TypeError as exc: From 61d42eb148b67e4413778d5ab8eab38aaa8b4c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Sun, 4 Jul 2021 12:51:37 -0400 Subject: [PATCH 4/7] try to use overload instead of / --- toolz/functoolz.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/toolz/functoolz.py b/toolz/functoolz.py index b0135518..1367eb5a 100644 --- a/toolz/functoolz.py +++ b/toolz/functoolz.py @@ -1,17 +1,16 @@ -from __future__ import annotations +from __future__ import annotations -from functools import reduce, partial import inspect import sys -from operator import attrgetter, not_ +from functools import partial, reduce from importlib import import_module +from operator import attrgetter, not_ from textwrap import dedent from types import MethodType -from typing import Any, Callable, Generic, TypeVar, Union +from typing import Any, Callable, Generic, TypeVar, Union, overload from .utils import no_default - __all__ = ('identity', 'apply', 'thread_first', 'thread_last', 'memoize', 'compose', 'compose_left', 'pipe', 'complement', 'juxt', 'do', 'curry', 'flip', 'excepts') @@ -196,7 +195,13 @@ class curry(Generic[_T]): toolz.curried - namespace of curried functions https://toolz.readthedocs.io/en/latest/curry.html """ - def __init__(self, func: Callable[..., _T], /, *args: Any, **kwargs: Any) -> None: + @overload + def __init__(self, func: Callable[..., _T], *args: Any, **kwargs: Any) -> None: ... + + def __init__(self, *args: Any, **kwargs: Any) -> None: + if not args: + raise TypeError('__init__() takes at least 2 arguments (1 given)') + func, args = args[0], args[1:] if not callable(func): raise TypeError("Input must be callable") From 81c412c60d4102da79a0ce30470e6171f0f19b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Sun, 4 Jul 2021 13:10:53 -0400 Subject: [PATCH 5/7] annotate func, bind, and call --- toolz/functoolz.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/toolz/functoolz.py b/toolz/functoolz.py index 1367eb5a..ca4522eb 100644 --- a/toolz/functoolz.py +++ b/toolz/functoolz.py @@ -233,7 +233,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self._has_unknown_args = None @instanceproperty - def func(self): + def func(self) -> Callable[..., _T]: return self._partial.func @instanceproperty @@ -338,10 +338,10 @@ def _should_curry(self, args, kwargs, exc=None): # There was a genuine TypeError return False - def bind(self, *args, **kwargs): + def bind(self, *args, **kwargs) -> curry[_T]: return type(self)(self, *args, **kwargs) - def call(self, *args, **kwargs): + def call(self, *args, **kwargs) -> _T: return self._partial(*args, **kwargs) def __get__(self, instance, owner): From bc8479231ec865643af7eb7af4d16ab2433f6fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Thu, 8 Jul 2021 16:38:34 -0400 Subject: [PATCH 6/7] mypy wants at least two overloads --- toolz/functoolz.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/toolz/functoolz.py b/toolz/functoolz.py index ca4522eb..29a4eb25 100644 --- a/toolz/functoolz.py +++ b/toolz/functoolz.py @@ -7,7 +7,7 @@ from operator import attrgetter, not_ from textwrap import dedent from types import MethodType -from typing import Any, Callable, Generic, TypeVar, Union, overload +from typing import Any, Callable, Dict, Generic, TypeVar, Union, overload from .utils import no_default @@ -198,6 +198,10 @@ class curry(Generic[_T]): @overload def __init__(self, func: Callable[..., _T], *args: Any, **kwargs: Any) -> None: ... + # this overload should never be used, mypy complains if only one overload exists + @overload + def __init__(self, *args: Union[Callable[..., _T], Any], **kwargs: Any) -> None: ... + def __init__(self, *args: Any, **kwargs: Any) -> None: if not args: raise TypeError('__init__() takes at least 2 arguments (1 given)') @@ -279,29 +283,29 @@ def args(self): return self._partial.args @instanceproperty - def keywords(self): + def keywords(self) -> Dict[str, Any]: return self._partial.keywords @instanceproperty - def func_name(self): + def func_name(self) -> str: return self.__name__ - def __str__(self): + def __str__(self) -> str: return str(self.func) - def __repr__(self): + def __repr__(self) -> str: return repr(self.func) - def __hash__(self): + def __hash__(self) -> int: return hash((self.func, self.args, frozenset(self.keywords.items()) if self.keywords else None)) - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return (isinstance(other, curry) and self.func == other.func and self.args == other.args and self.keywords == other.keywords) - def __ne__(self, other): + def __ne__(self, other: Any) -> bool: return not self.__eq__(other) def __call__(self, *args: Any, **kwargs: Any) -> Union[_T, curry[_T]]: From 4171cf00dae791cf3ec7d66ac49d59d3df7c1ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20W=C3=B6rtwein?= Date: Fri, 9 Jul 2021 11:42:44 -0400 Subject: [PATCH 7/7] some low-hanging typing in functoolz.py --- toolz/functoolz.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/toolz/functoolz.py b/toolz/functoolz.py index 29a4eb25..f71347c3 100644 --- a/toolz/functoolz.py +++ b/toolz/functoolz.py @@ -17,9 +17,10 @@ PYPY = hasattr(sys, 'pypy_version_info') _T = TypeVar("_T") +_T2 = TypeVar("_T2") -def identity(x): +def identity(x: _T) ->_T: """ Identity function. Return x >>> identity(3) @@ -687,7 +688,7 @@ def __setstate__(self, state): self.funcs = state -def do(func, x): +def do(func: Callable[[_T], Any], x: _T) -> _T: """ Runs ``func`` on ``x``, returns ``x`` Because the results of ``func`` are not returned, only the side @@ -714,7 +715,7 @@ def do(func, x): @curry -def flip(func, a, b): +def flip(func: Callable[[_T, _T], _T2], a: _T, b: _T) -> _T2: """ Call the function call with the arguments flipped This function is curried. @@ -740,7 +741,7 @@ def flip(func, a, b): return func(b, a) -def return_none(exc): +def return_none(exc: Any) -> None: """ Returns None. """ return None