From e29fb98dc35e8c7fb9c4175994b2efbd213b2d4f Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Thu, 11 Nov 2021 16:15:36 +0700 Subject: [PATCH] Use inspect.signature() to inspect functions in Python 3 Give up on `str.endswith` and other non-introspectable things for now. Adjust docs. Improves on #108. --- README.rst | 2 +- docs/funcs.rst | 2 +- funcy/_inspect.py | 33 ++++++++++++++++++++++++++++++--- tests/test_funcs.py | 4 +++- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 6c520de..93e1ce7 100644 --- a/README.rst +++ b/README.rst @@ -92,7 +92,7 @@ And functions: all_fn(isa(int), even) # is_even_int one_third = rpartial(operator.div, 3.0) - has_suffix = rcurry(str.endswith) + has_suffix = rcurry(str.endswith, 2) Create decorators easily: diff --git a/docs/funcs.rst b/docs/funcs.rst index 47e75c3..8c14e12 100644 --- a/docs/funcs.rst +++ b/docs/funcs.rst @@ -65,7 +65,7 @@ Functions Curries function from last argument to first:: - has_suffix = rcurry(str.endswith) + has_suffix = rcurry(str.endswith, 2) lfilter(has_suffix("ce"), ["nice", "cold", "ice"]) # -> ["nice", "ice"] diff --git a/funcy/_inspect.py b/funcy/_inspect.py index 2e4dfa9..0170ab0 100644 --- a/funcy/_inspect.py +++ b/funcy/_inspect.py @@ -1,5 +1,9 @@ from __future__ import absolute_import from inspect import CO_VARARGS, CO_VARKEYWORDS +try: + from inspect import signature +except ImportError: + signature = None # from collections import namedtuple import types import re @@ -105,7 +109,7 @@ def get_spec(func, _cache={}): except (KeyError, TypeError): pass - mod = func.__module__ + mod = getattr(func, '__module__', None) if mod in STD_MODULES or mod in ARGS and func.__name__ in ARGS[mod]: _spec = ARGS[mod].get(func.__name__, '*') required, _, optional = _spec.partition('-') @@ -144,5 +148,28 @@ def get_spec(func, _cache={}): max_n = req_n + 1 if func.__code__.co_flags & CO_VARARGS else n return Spec(max_n=max_n, names=names, req_n=req_n, req_names=req_names, kw=kw) except AttributeError: - raise ValueError('Unable to introspect %s() arguments' - % getattr(func, '__name__', func)) + # We use signature last to be fully backwards compatible. Also it's slower + try: + sig = signature(func) + except (ValueError, TypeError): + raise ValueError('Unable to introspect %s() arguments' + % getattr(func, '__qualname__', None) or getattr(func, '__name__', func)) + else: + spec = _cache[func] = _sig_to_spec(sig) + return spec + + +def _sig_to_spec(sig): + max_n, names, req_n, req_names, kw = 0, set(), 0, set(), False + for name, param in sig.parameters.items(): + max_n += 1 + if param.kind == param.VAR_KEYWORD: + kw = True + elif param.kind == param.VAR_POSITIONAL: + req_n += 1 + else: + names.add(name) + if param.default is param.empty: + req_n += 1 + req_names.add(name) + return Spec(max_n=max_n, names=names, req_n=req_n, req_names=req_names, kw=kw) diff --git a/tests/test_funcs.py b/tests/test_funcs.py index 8db6323..e70f6c1 100644 --- a/tests/test_funcs.py +++ b/tests/test_funcs.py @@ -28,7 +28,7 @@ class A(object): assert A().f() == 11 -def test_back_partial(): +def test_rpartial(): assert rpartial(__sub__, 10)(1) == -9 assert rpartial(pow, 2, 85)(10) == 15 @@ -53,6 +53,7 @@ def test_curry_funcy(): def test_rcurry(): assert rcurry(__sub__, 2)(10)(1) == -9 assert rcurry(lambda x,y,z: x+y+z)('a')('b')('c') == 'cba' + assert rcurry(str.endswith, 2)('c')('abc') is True def test_autocurry(): at = autocurry(lambda a, b, c: (a, b, c)) @@ -97,6 +98,7 @@ def test_autocurry_builtin(): assert autocurry(complex)(imag=1)(0) == 1j assert autocurry(map)(_ + 1)([1, 2]) == [2, 3] assert autocurry(int)(base=12)('100') == 144 + assert autocurry(str.split)(sep='_')('a_1') == ['a', '1'] def test_autocurry_hard(): def required_star(f, *seqs):