Skip to content

Commit

Permalink
Use inspect.signature() to inspect functions in Python 3
Browse files Browse the repository at this point in the history
Give up on `str.endswith` and other non-introspectable things for now.
Adjust docs.

Improves on #108.
  • Loading branch information
Suor committed Nov 11, 2021
1 parent b5efc42 commit e29fb98
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 6 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion docs/funcs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
33 changes: 30 additions & 3 deletions funcy/_inspect.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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('-')
Expand Down Expand Up @@ -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)
4 changes: 3 additions & 1 deletion tests/test_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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))
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit e29fb98

Please sign in to comment.