Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/toolz/functoolz.py: 40%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from functools import reduce, partial
2import inspect
3import sys
4from operator import attrgetter, not_
5from importlib import import_module
6from types import MethodType
8from .utils import no_default
10if sys.version_info >= (3, 14):
11 import annotationlib # already imported by inspect
14__all__ = ('identity', 'apply', 'thread_first', 'thread_last', 'memoize',
15 'compose', 'compose_left', 'pipe', 'complement', 'juxt', 'do',
16 'curry', 'flip', 'excepts')
18PYPY = hasattr(sys, 'pypy_version_info')
21def identity(x):
22 """ Identity function. Return x
24 >>> identity(3)
25 3
26 """
27 return x
30def apply(*func_and_args, **kwargs):
31 """ Applies a function and returns the results
33 >>> def double(x): return 2*x
34 >>> def inc(x): return x + 1
35 >>> apply(double, 5)
36 10
38 >>> tuple(map(apply, [double, inc, double], [10, 500, 8000]))
39 (20, 501, 16000)
40 """
41 if not func_and_args:
42 raise TypeError('func argument is required')
43 func, args = func_and_args[0], func_and_args[1:]
44 return func(*args, **kwargs)
47def thread_first(val, *forms):
48 """ Thread value through a sequence of functions/forms
50 >>> def double(x): return 2*x
51 >>> def inc(x): return x + 1
52 >>> thread_first(1, inc, double)
53 4
55 If the function expects more than one input you can specify those inputs
56 in a tuple. The value is used as the first input.
58 >>> def add(x, y): return x + y
59 >>> def pow(x, y): return x**y
60 >>> thread_first(1, (add, 4), (pow, 2)) # pow(add(1, 4), 2)
61 25
63 So in general
64 thread_first(x, f, (g, y, z))
65 expands to
66 g(f(x), y, z)
68 See Also:
69 thread_last
70 """
71 def evalform_front(val, form):
72 if callable(form):
73 return form(val)
74 if isinstance(form, tuple):
75 func, args = form[0], form[1:]
76 args = (val,) + args
77 return func(*args)
78 return reduce(evalform_front, forms, val)
81def thread_last(val, *forms):
82 """ Thread value through a sequence of functions/forms
84 >>> def double(x): return 2*x
85 >>> def inc(x): return x + 1
86 >>> thread_last(1, inc, double)
87 4
89 If the function expects more than one input you can specify those inputs
90 in a tuple. The value is used as the last input.
92 >>> def add(x, y): return x + y
93 >>> def pow(x, y): return x**y
94 >>> thread_last(1, (add, 4), (pow, 2)) # pow(2, add(4, 1))
95 32
97 So in general
98 thread_last(x, f, (g, y, z))
99 expands to
100 g(y, z, f(x))
102 >>> def iseven(x):
103 ... return x % 2 == 0
104 >>> list(thread_last([1, 2, 3], (map, inc), (filter, iseven)))
105 [2, 4]
107 See Also:
108 thread_first
109 """
110 def evalform_back(val, form):
111 if callable(form):
112 return form(val)
113 if isinstance(form, tuple):
114 func, args = form[0], form[1:]
115 args = args + (val,)
116 return func(*args)
117 return reduce(evalform_back, forms, val)
120def instanceproperty(fget=None, fset=None, fdel=None, doc=None, classval=None):
121 """ Like @property, but returns ``classval`` when used as a class attribute
123 >>> class MyClass(object):
124 ... '''The class docstring'''
125 ... @instanceproperty(classval=__doc__)
126 ... def __doc__(self):
127 ... return 'An object docstring'
128 ... @instanceproperty
129 ... def val(self):
130 ... return 42
131 ...
132 >>> MyClass.__doc__
133 'The class docstring'
134 >>> MyClass.val is None
135 True
136 >>> obj = MyClass()
137 >>> obj.__doc__
138 'An object docstring'
139 >>> obj.val
140 42
141 """
142 if fget is None:
143 return partial(instanceproperty, fset=fset, fdel=fdel, doc=doc,
144 classval=classval)
145 return InstanceProperty(fget=fget, fset=fset, fdel=fdel, doc=doc,
146 classval=classval)
149class InstanceProperty(property):
150 """ Like @property, but returns ``classval`` when used as a class attribute
152 Should not be used directly. Use ``instanceproperty`` instead.
153 """
154 def __init__(self, fget=None, fset=None, fdel=None, doc=None,
155 classval=None):
156 self.classval = classval
157 property.__init__(self, fget=fget, fset=fset, fdel=fdel, doc=doc)
159 def __get__(self, obj, type=None):
160 if obj is None:
161 return self.classval
162 return property.__get__(self, obj, type)
164 def __reduce__(self):
165 state = (self.fget, self.fset, self.fdel, self.__doc__, self.classval)
166 return InstanceProperty, state
169class curry:
170 """ Curry a callable function
172 Enables partial application of arguments through calling a function with an
173 incomplete set of arguments.
175 >>> def mul(x, y):
176 ... return x * y
177 >>> mul = curry(mul)
179 >>> double = mul(2)
180 >>> double(10)
181 20
183 Also supports keyword arguments
185 >>> @curry # Can use curry as a decorator
186 ... def f(x, y, a=10):
187 ... return a * (x + y)
189 >>> add = f(a=1)
190 >>> add(2, 3)
191 5
193 See Also:
194 toolz.curried - namespace of curried functions
195 https://toolz.readthedocs.io/en/latest/curry.html
196 """
197 def __init__(self, *args, **kwargs):
198 if not args:
199 raise TypeError('__init__() takes at least 2 arguments (1 given)')
200 func, args = args[0], args[1:]
201 if not callable(func):
202 raise TypeError("Input must be callable")
204 # curry- or functools.partial-like object? Unpack and merge arguments
205 if (
206 hasattr(func, 'func')
207 and hasattr(func, 'args')
208 and hasattr(func, 'keywords')
209 and isinstance(func.args, tuple)
210 ):
211 _kwargs = {}
212 if func.keywords:
213 _kwargs.update(func.keywords)
214 _kwargs.update(kwargs)
215 kwargs = _kwargs
216 args = func.args + args
217 func = func.func
219 if kwargs:
220 self._partial = partial(func, *args, **kwargs)
221 else:
222 self._partial = partial(func, *args)
224 self.__doc__ = getattr(func, '__doc__', None)
225 self.__name__ = getattr(func, '__name__', '<curry>')
226 self.__module__ = getattr(func, '__module__', None)
227 self.__qualname__ = getattr(func, '__qualname__', None)
228 self._sigspec = None
229 self._has_unknown_args = None
231 @instanceproperty
232 def func(self):
233 return self._partial.func
235 @instanceproperty
236 def __signature__(self):
237 sig = inspect.signature(self.func)
238 args = self.args or ()
239 keywords = self.keywords or {}
240 if is_partial_args(self.func, args, keywords, sig) is False:
241 raise TypeError('curry object has incorrect arguments')
243 params = list(sig.parameters.values())
244 skip = 0
245 for param in params[:len(args)]:
246 if param.kind == param.VAR_POSITIONAL:
247 break
248 skip += 1
250 kwonly = False
251 newparams = []
252 for param in params[skip:]:
253 kind = param.kind
254 default = param.default
255 if kind == param.VAR_KEYWORD:
256 pass
257 elif kind == param.VAR_POSITIONAL:
258 if kwonly:
259 continue
260 elif param.name in keywords:
261 default = keywords[param.name]
262 kind = param.KEYWORD_ONLY
263 kwonly = True
264 else:
265 if kwonly:
266 kind = param.KEYWORD_ONLY
267 if default is param.empty:
268 default = no_default
269 newparams.append(param.replace(default=default, kind=kind))
271 return sig.replace(parameters=newparams)
273 @instanceproperty
274 def args(self):
275 return self._partial.args
277 @instanceproperty
278 def keywords(self):
279 return self._partial.keywords
281 @instanceproperty
282 def func_name(self):
283 return self.__name__
285 def __str__(self):
286 return str(self.func)
288 def __repr__(self):
289 return repr(self.func)
291 def __hash__(self):
292 return hash((self.func, self.args,
293 frozenset(self.keywords.items()) if self.keywords
294 else None))
296 def __eq__(self, other):
297 return (isinstance(other, curry) and self.func == other.func and
298 self.args == other.args and self.keywords == other.keywords)
300 def __ne__(self, other):
301 return not self.__eq__(other)
303 def __call__(self, *args, **kwargs):
304 try:
305 return self._partial(*args, **kwargs)
306 except TypeError as exc:
307 if self._should_curry(args, kwargs, exc):
308 return self.bind(*args, **kwargs)
309 raise
311 def _should_curry(self, args, kwargs, exc=None):
312 func = self.func
313 args = self.args + args
314 if self.keywords:
315 kwargs = dict(self.keywords, **kwargs)
316 if self._sigspec is None:
317 sigspec = self._sigspec = _sigs.signature_or_spec(func)
318 self._has_unknown_args = has_varargs(func, sigspec) is not False
319 else:
320 sigspec = self._sigspec
322 if is_partial_args(func, args, kwargs, sigspec) is False:
323 # Nothing can make the call valid
324 return False
325 elif self._has_unknown_args:
326 # The call may be valid and raised a TypeError, but we curry
327 # anyway because the function may have `*args`. This is useful
328 # for decorators with signature `func(*args, **kwargs)`.
329 return True
330 elif not is_valid_args(func, args, kwargs, sigspec):
331 # Adding more arguments may make the call valid
332 return True
333 else:
334 # There was a genuine TypeError
335 return False
337 def bind(self, *args, **kwargs):
338 return type(self)(self, *args, **kwargs)
340 def call(self, *args, **kwargs):
341 return self._partial(*args, **kwargs)
343 def __get__(self, instance, owner):
344 if instance is None:
345 return self
346 return curry(self, instance)
348 def __reduce__(self):
349 func = self.func
350 modname = getattr(func, '__module__', None)
351 qualname = getattr(func, '__qualname__', None)
352 if qualname is None: # pragma: no cover
353 qualname = getattr(func, '__name__', None)
354 is_decorated = None
355 if modname and qualname:
356 attrs = []
357 obj = import_module(modname)
358 for attr in qualname.split('.'):
359 if isinstance(obj, curry):
360 attrs.append('func')
361 obj = obj.func
362 obj = getattr(obj, attr, None)
363 if obj is None:
364 break
365 attrs.append(attr)
366 if isinstance(obj, curry) and obj.func is func:
367 is_decorated = obj is self
368 qualname = '.'.join(attrs)
369 func = f'{modname}:{qualname}'
371 # functools.partial objects can't be pickled
372 userdict = tuple((k, v) for k, v in self.__dict__.items()
373 if k not in ('_partial', '_sigspec'))
374 state = (type(self), func, self.args, self.keywords, userdict,
375 is_decorated)
376 return _restore_curry, state
379def _restore_curry(cls, func, args, kwargs, userdict, is_decorated):
380 if isinstance(func, str):
381 modname, qualname = func.rsplit(':', 1)
382 obj = import_module(modname)
383 for attr in qualname.split('.'):
384 obj = getattr(obj, attr)
385 if is_decorated:
386 return obj
387 func = obj.func
388 obj = cls(func, *args, **(kwargs or {}))
389 obj.__dict__.update(userdict)
390 return obj
393@curry
394def memoize(func, cache=None, key=None):
395 """ Cache a function's result for speedy future evaluation
397 Considerations:
398 Trades memory for speed.
399 Only use on pure functions.
401 >>> def add(x, y): return x + y
402 >>> add = memoize(add)
404 Or use as a decorator
406 >>> @memoize
407 ... def add(x, y):
408 ... return x + y
410 Use the ``cache`` keyword to provide a dict-like object as an initial cache
412 >>> @memoize(cache={(1, 2): 3})
413 ... def add(x, y):
414 ... return x + y
416 Note that the above works as a decorator because ``memoize`` is curried.
418 It is also possible to provide a ``key(args, kwargs)`` function that
419 calculates keys used for the cache, which receives an ``args`` tuple and
420 ``kwargs`` dict as input, and must return a hashable value. However,
421 the default key function should be sufficient most of the time.
423 >>> # Use key function that ignores extraneous keyword arguments
424 >>> @memoize(key=lambda args, kwargs: args)
425 ... def add(x, y, verbose=False):
426 ... if verbose:
427 ... print('Calculating %s + %s' % (x, y))
428 ... return x + y
429 """
430 if cache is None:
431 cache = {}
433 try:
434 may_have_kwargs = has_keywords(func) is not False
435 # Is unary function (single arg, no variadic argument or keywords)?
436 is_unary = is_arity(1, func)
437 except TypeError: # pragma: no cover
438 may_have_kwargs = True
439 is_unary = False
441 if key is None:
442 if is_unary:
443 def key(args, kwargs):
444 return args[0]
445 elif may_have_kwargs:
446 def key(args, kwargs):
447 return (
448 args or None,
449 frozenset(kwargs.items()) if kwargs else None,
450 )
451 else:
452 def key(args, kwargs):
453 return args
455 def memof(*args, **kwargs):
456 k = key(args, kwargs)
457 try:
458 return cache[k]
459 except TypeError:
460 raise TypeError("Arguments to memoized function must be hashable")
461 except KeyError:
462 cache[k] = result = func(*args, **kwargs)
463 return result
465 try:
466 memof.__name__ = func.__name__
467 except AttributeError:
468 pass
469 memof.__doc__ = func.__doc__
470 memof.__wrapped__ = func
471 return memof
474class _InstanceAnnotations(dict):
475 """ Descriptor for an ``__annotations__`` computed by ``fget`` on instances
477 Accessed on the class, this is an empty dict, because ``typing`` and
478 ``inspect`` require a class's ``__annotations__`` to be a dict (before
479 Python 3.14 they read it directly from the class ``__dict__``).
480 """
481 __slots__ = ('fget',)
483 def __init__(self, fget):
484 self.fget = fget
486 def __get__(self, obj, type=None):
487 return self if obj is None else self.fget(obj)
490class Compose:
491 """ A composition of functions
493 See Also:
494 compose
495 """
496 __slots__ = 'first', 'funcs'
498 def __init__(self, funcs):
499 funcs = tuple(reversed(funcs))
500 self.first = funcs[0]
501 self.funcs = funcs[1:]
503 def __call__(self, *args, **kwargs):
504 ret = self.first(*args, **kwargs)
505 for f in self.funcs:
506 ret = f(ret)
507 return ret
509 def __getstate__(self):
510 return self.first, self.funcs
512 def __setstate__(self, state):
513 self.first, self.funcs = state
515 @instanceproperty(classval=__doc__)
516 def __doc__(self):
517 def composed_doc(*fs):
518 """Generate a docstring for the composition of fs.
519 """
520 if not fs:
521 # Argument name for the docstring.
522 return '*args, **kwargs'
524 return f'{fs[0].__name__}({composed_doc(*fs[1:])})'
526 try:
527 return (
528 'lambda *args, **kwargs: ' +
529 composed_doc(*reversed((self.first,) + self.funcs))
530 )
531 except AttributeError:
532 # One of our callables does not have a `__name__`, whatever.
533 return 'A composition of functions'
535 @property
536 def __name__(self):
537 try:
538 return '_of_'.join(
539 f.__name__ for f in reversed((self.first,) + self.funcs)
540 )
541 except AttributeError:
542 return type(self).__name__
544 def __repr__(self):
545 return '{.__class__.__name__}{!r}'.format(
546 self, tuple(reversed((self.first, ) + self.funcs)))
548 def __eq__(self, other):
549 if isinstance(other, Compose):
550 return other.first == self.first and other.funcs == self.funcs
551 return NotImplemented
553 def __ne__(self, other):
554 equality = self.__eq__(other)
555 return NotImplemented if equality is NotImplemented else not equality
557 def __hash__(self):
558 return hash(self.first) ^ hash(self.funcs)
560 # Mimic the descriptor behavior of python functions.
561 # i.e. let Compose be called as a method when bound to a class.
562 # adapted from
563 # docs.python.org/3/howto/descriptor.html#functions-and-methods
564 def __get__(self, obj, objtype=None):
565 return self if obj is None else MethodType(self, obj)
567 # introspection with Signature is only possible from py3.3+
568 @instanceproperty
569 def __signature__(self):
570 base = inspect.signature(self.first)
571 last = inspect.signature(self.funcs[-1])
572 return base.replace(return_annotation=last.return_annotation)
574 __wrapped__ = instanceproperty(attrgetter('first'))
576 def _combined_annotations(self, annotation_format=None):
577 """ Combined type annotations for the composed callable.
579 Parameter annotations come from the first function applied in the
580 composition, and the return annotation comes from the last function
581 applied, as ``inspect.signature`` reports them (so this agrees with
582 ``__signature__``). Functions without a signature contribute nothing.
584 ``annotation_format`` (Python 3.14+) is passed to
585 ``inspect.signature``. With ``FORWARDREF`` or ``STRING``, which must
586 not raise ``NameError``, a function whose ``__signature__`` evaluates
587 annotations eagerly (e.g. ``curry``) and fails also contributes
588 nothing.
590 String annotations are returned as-is; ``typing.get_type_hints``
591 evaluates them in the namespace of the first function.
592 """
593 if annotation_format is None:
594 kwargs = {}
595 lazy = False
596 else:
597 kwargs = {'annotation_format': annotation_format}
598 lazy = annotation_format != annotationlib.Format.VALUE
600 def annotations_of(func):
601 if isinstance(func, Compose):
602 return func._combined_annotations(annotation_format)
603 try:
604 sig = inspect.signature(func, **kwargs)
605 except (TypeError, ValueError):
606 return {}
607 except NameError:
608 if not lazy:
609 raise
610 return {}
611 rv = {name: param.annotation
612 for name, param in sig.parameters.items()
613 if param.annotation is not param.empty}
614 if sig.return_annotation is not sig.empty:
615 rv['return'] = sig.return_annotation
616 return rv
618 annotations = annotations_of(self.first)
619 annotations.pop('return', None)
620 last = self.funcs[-1] if self.funcs else self.first
621 last_annotations = annotations_of(last)
622 if 'return' in last_annotations:
623 annotations['return'] = last_annotations['return']
624 return annotations
626 # Lets ``typing.get_type_hints`` and ``inspect.get_annotations`` see the
627 # same types as ``__signature__``.
628 __annotations__ = _InstanceAnnotations(_combined_annotations)
630 if sys.version_info >= (3, 14):
631 def _get_annotate(self):
632 def annotate(format):
633 Format = annotationlib.Format
634 if format == Format.STRING:
635 # Some ``__signature__`` (e.g. ``curry``) ignore the
636 # format and give values, so stringify what we got.
637 return annotationlib.annotations_to_string(
638 self._combined_annotations(format)
639 )
640 if format in (Format.VALUE, Format.FORWARDREF):
641 return self._combined_annotations(format)
642 raise NotImplementedError(format)
643 return annotate
645 # PEP 649/749: support the other annotation formats (``FORWARDREF``,
646 # ``STRING``) and let ``functools.wraps`` copy the annotations.
647 # Not ``def __annotate__``: coverage.py never traces functions with
648 # that name, since it assumes they are compiler-generated.
649 __annotate__ = instanceproperty(_get_annotate)
652def compose(*funcs):
653 """ Compose functions to operate in series.
655 Returns a function that applies other functions in sequence.
657 Functions are applied from right to left so that
658 ``compose(f, g, h)(x, y)`` is the same as ``f(g(h(x, y)))``.
660 If no arguments are provided, the identity function (f(x) = x) is returned.
662 >>> inc = lambda i: i + 1
663 >>> compose(str, inc)(3)
664 '4'
666 See Also:
667 compose_left
668 pipe
669 """
670 if not funcs:
671 return identity
672 if len(funcs) == 1:
673 return funcs[0]
674 else:
675 return Compose(funcs)
678def compose_left(*funcs):
679 """ Compose functions to operate in series.
681 Returns a function that applies other functions in sequence.
683 Functions are applied from left to right so that
684 ``compose_left(f, g, h)(x, y)`` is the same as ``h(g(f(x, y)))``.
686 If no arguments are provided, the identity function (f(x) = x) is returned.
688 >>> inc = lambda i: i + 1
689 >>> compose_left(inc, str)(3)
690 '4'
692 See Also:
693 compose
694 pipe
695 """
696 return compose(*reversed(funcs))
699def pipe(data, *funcs):
700 """ Pipe a value through a sequence of functions
702 I.e. ``pipe(data, f, g, h)`` is equivalent to ``h(g(f(data)))``
704 We think of the value as progressing through a pipe of several
705 transformations, much like pipes in UNIX
707 ``$ cat data | f | g | h``
709 >>> double = lambda i: 2 * i
710 >>> pipe(3, double, str)
711 '6'
713 See Also:
714 compose
715 compose_left
716 thread_first
717 thread_last
718 """
719 for func in funcs:
720 data = func(data)
721 return data
724def complement(func):
725 """ Convert a predicate function to its logical complement.
727 In other words, return a function that, for inputs that normally
728 yield True, yields False, and vice-versa.
730 >>> def iseven(n): return n % 2 == 0
731 >>> isodd = complement(iseven)
732 >>> iseven(2)
733 True
734 >>> isodd(2)
735 False
736 """
737 return compose(not_, func)
740class juxt:
741 """ Creates a function that calls several functions with the same arguments
743 Takes several functions and returns a function that applies its arguments
744 to each of those functions then returns a tuple of the results.
746 Name comes from juxtaposition: the fact of two things being seen or placed
747 close together with contrasting effect.
749 >>> inc = lambda x: x + 1
750 >>> double = lambda x: x * 2
751 >>> juxt(inc, double)(10)
752 (11, 20)
753 >>> juxt([inc, double])(10)
754 (11, 20)
755 """
756 __slots__ = ['funcs']
758 def __init__(self, *funcs):
759 if len(funcs) == 1 and not callable(funcs[0]):
760 funcs = funcs[0]
761 self.funcs = tuple(funcs)
763 def __call__(self, *args, **kwargs):
764 return tuple(func(*args, **kwargs) for func in self.funcs)
766 def __getstate__(self):
767 return self.funcs
769 def __setstate__(self, state):
770 self.funcs = state
773def do(func, x):
774 """ Runs ``func`` on ``x``, returns ``x``
776 Because the results of ``func`` are not returned, only the side
777 effects of ``func`` are relevant.
779 Logging functions can be made by composing ``do`` with a storage function
780 like ``list.append`` or ``file.write``
782 >>> from toolz import compose
783 >>> from toolz.curried import do
785 >>> log = []
786 >>> inc = lambda x: x + 1
787 >>> inc = compose(inc, do(log.append))
788 >>> inc(1)
789 2
790 >>> inc(11)
791 12
792 >>> log
793 [1, 11]
794 """
795 func(x)
796 return x
799@curry
800def flip(func, a, b):
801 """ Call the function call with the arguments flipped
803 This function is curried.
805 >>> def div(a, b):
806 ... return a // b
807 ...
808 >>> flip(div, 2, 6)
809 3
810 >>> div_by_two = flip(div, 2)
811 >>> div_by_two(4)
812 2
814 This is particularly useful for built in functions and functions defined
815 in C extensions that accept positional only arguments. For example:
816 isinstance, issubclass.
818 >>> data = [1, 'a', 'b', 2, 1.5, object(), 3]
819 >>> only_ints = list(filter(flip(isinstance, int), data))
820 >>> only_ints
821 [1, 2, 3]
822 """
823 return func(b, a)
826def return_none(exc):
827 """ Returns None.
828 """
829 return None
832class excepts:
833 """A wrapper around a function to catch exceptions and
834 dispatch to a handler.
836 This is like a functional try/except block, in the same way that
837 ifexprs are functional if/else blocks.
839 Examples
840 --------
841 >>> excepting = excepts(
842 ... ValueError,
843 ... lambda a: [1, 2].index(a),
844 ... lambda _: -1,
845 ... )
846 >>> excepting(1)
847 0
848 >>> excepting(3)
849 -1
851 Multiple exceptions and default except clause.
853 >>> excepting = excepts((IndexError, KeyError), lambda a: a[0])
854 >>> excepting([])
855 >>> excepting([1])
856 1
857 >>> excepting({})
858 >>> excepting({0: 1})
859 1
860 """
861 def __init__(self, exc, func, handler=return_none):
862 self.exc = exc
863 self.func = func
864 self.handler = handler
866 def __call__(self, *args, **kwargs):
867 try:
868 return self.func(*args, **kwargs)
869 except self.exc as e:
870 return self.handler(e)
872 @instanceproperty(classval=__doc__)
873 def __doc__(self):
874 from textwrap import dedent
876 exc = self.exc
877 try:
878 if isinstance(exc, tuple):
879 exc_name = '(%s)' % ', '.join(
880 map(attrgetter('__name__'), exc),
881 )
882 else:
883 exc_name = exc.__name__
885 return dedent(
886 """\
887 A wrapper around {inst.func.__name__!r} that will except:
888 {exc}
889 and handle any exceptions with {inst.handler.__name__!r}.
891 Docs for {inst.func.__name__!r}:
892 {inst.func.__doc__}
894 Docs for {inst.handler.__name__!r}:
895 {inst.handler.__doc__}
896 """
897 ).format(
898 inst=self,
899 exc=exc_name,
900 )
901 except AttributeError:
902 return type(self).__doc__
904 @property
905 def __name__(self):
906 exc = self.exc
907 try:
908 if isinstance(exc, tuple):
909 exc_name = '_or_'.join(map(attrgetter('__name__'), exc))
910 else:
911 exc_name = exc.__name__
912 return f'{self.func.__name__}_excepting_{exc_name}'
913 except AttributeError:
914 return 'excepting'
917def _check_sigspec(sigspec, func, builtin_func, *builtin_args):
918 if sigspec is None:
919 try:
920 sigspec = inspect.signature(func)
921 except (ValueError, TypeError) as e:
922 sigspec = e
923 if isinstance(sigspec, ValueError):
924 return None, builtin_func(*builtin_args)
925 elif not isinstance(sigspec, inspect.Signature):
926 if (
927 func in _sigs.signatures
928 and (
929 hasattr(func, '__signature__')
930 and hasattr(func.__signature__, '__get__')
931 )
932 ):
933 val = builtin_func(*builtin_args)
934 return None, val
935 return None, False
936 return sigspec, None
939if PYPY: # pragma: no cover
940 _check_sigspec_orig = _check_sigspec
942 def _check_sigspec(sigspec, func, builtin_func, *builtin_args):
943 # PyPy may lie, so use our registry for builtins instead
944 if func in _sigs.signatures:
945 val = builtin_func(*builtin_args)
946 return None, val
947 return _check_sigspec_orig(sigspec, func, builtin_func, *builtin_args)
950_check_sigspec.__doc__ = """ \
951Private function to aid in introspection compatibly across Python versions.
953If a callable doesn't have a signature (Python 3) or an argspec (Python 2),
954the signature registry in toolz._signatures is used.
955"""
958def num_required_args(func, sigspec=None):
959 sigspec, rv = _check_sigspec(sigspec, func, _sigs._num_required_args,
960 func)
961 if sigspec is None:
962 return rv
963 return sum(1 for p in sigspec.parameters.values()
964 if p.default is p.empty
965 and p.kind in (p.POSITIONAL_OR_KEYWORD, p.POSITIONAL_ONLY))
968def has_varargs(func, sigspec=None):
969 sigspec, rv = _check_sigspec(sigspec, func, _sigs._has_varargs, func)
970 if sigspec is None:
971 return rv
972 return any(p.kind == p.VAR_POSITIONAL
973 for p in sigspec.parameters.values())
976def has_keywords(func, sigspec=None):
977 sigspec, rv = _check_sigspec(sigspec, func, _sigs._has_keywords, func)
978 if sigspec is None:
979 return rv
980 return any(p.default is not p.empty
981 or p.kind in (p.KEYWORD_ONLY, p.VAR_KEYWORD)
982 for p in sigspec.parameters.values())
985def is_valid_args(func, args, kwargs, sigspec=None):
986 sigspec, rv = _check_sigspec(sigspec, func, _sigs._is_valid_args,
987 func, args, kwargs)
988 if sigspec is None:
989 return rv
990 try:
991 sigspec.bind(*args, **kwargs)
992 except TypeError:
993 return False
994 return True
997def is_partial_args(func, args, kwargs, sigspec=None):
998 sigspec, rv = _check_sigspec(sigspec, func, _sigs._is_partial_args,
999 func, args, kwargs)
1000 if sigspec is None:
1001 return rv
1002 try:
1003 sigspec.bind_partial(*args, **kwargs)
1004 except TypeError:
1005 return False
1006 return True
1009def is_arity(n, func, sigspec=None):
1010 """ Does a function have only n positional arguments?
1012 This function relies on introspection and does not call the function.
1013 Returns None if validity can't be determined.
1015 >>> def f(x):
1016 ... return x
1017 >>> is_arity(1, f)
1018 True
1019 >>> def g(x, y=1):
1020 ... return x + y
1021 >>> is_arity(1, g)
1022 False
1023 """
1024 sigspec, rv = _check_sigspec(sigspec, func, _sigs._is_arity, n, func)
1025 if sigspec is None:
1026 return rv
1027 num = num_required_args(func, sigspec)
1028 if num is not None:
1029 num = num == n
1030 if not num:
1031 return False
1032 varargs = has_varargs(func, sigspec)
1033 if varargs:
1034 return False
1035 keywords = has_keywords(func, sigspec)
1036 if keywords:
1037 return False
1038 if num is None or varargs is None or keywords is None: # pragma: no cover
1039 return None
1040 return True
1043num_required_args.__doc__ = """ \
1044Number of required positional arguments
1046 This function relies on introspection and does not call the function.
1047 Returns None if validity can't be determined.
1049 >>> def f(x, y, z=3):
1050 ... return x + y + z
1051 >>> num_required_args(f)
1052 2
1053 >>> def g(*args, **kwargs):
1054 ... pass
1055 >>> num_required_args(g)
1056 0
1057 """
1059has_varargs.__doc__ = """ \
1060Does a function have variadic positional arguments?
1062 This function relies on introspection and does not call the function.
1063 Returns None if validity can't be determined.
1065 >>> def f(*args):
1066 ... return args
1067 >>> has_varargs(f)
1068 True
1069 >>> def g(**kwargs):
1070 ... return kwargs
1071 >>> has_varargs(g)
1072 False
1073 """
1075has_keywords.__doc__ = """ \
1076Does a function have keyword arguments?
1078 This function relies on introspection and does not call the function.
1079 Returns None if validity can't be determined.
1081 >>> def f(x, y=0):
1082 ... return x + y
1084 >>> has_keywords(f)
1085 True
1086 """
1088is_valid_args.__doc__ = """ \
1089Is ``func(*args, **kwargs)`` a valid function call?
1091 This function relies on introspection and does not call the function.
1092 Returns None if validity can't be determined.
1094 >>> def add(x, y):
1095 ... return x + y
1097 >>> is_valid_args(add, (1,), {})
1098 False
1099 >>> is_valid_args(add, (1, 2), {})
1100 True
1101 >>> is_valid_args(map, (), {})
1102 False
1104 **Implementation notes**
1105 Python 2 relies on ``inspect.getargspec``, which only works for
1106 user-defined functions. Python 3 uses ``inspect.signature``, which
1107 works for many more types of callables.
1109 Many builtins in the standard library are also supported.
1110 """
1112is_partial_args.__doc__ = """ \
1113Can partial(func, *args, **kwargs)(*args2, **kwargs2) be a valid call?
1115 Returns True *only* if the call is valid or if it is possible for the
1116 call to become valid by adding more positional or keyword arguments.
1118 This function relies on introspection and does not call the function.
1119 Returns None if validity can't be determined.
1121 >>> def add(x, y):
1122 ... return x + y
1124 >>> is_partial_args(add, (1,), {})
1125 True
1126 >>> is_partial_args(add, (1, 2), {})
1127 True
1128 >>> is_partial_args(add, (1, 2, 3), {})
1129 False
1130 >>> is_partial_args(map, (), {})
1131 True
1133 **Implementation notes**
1134 Python 2 relies on ``inspect.getargspec``, which only works for
1135 user-defined functions. Python 3 uses ``inspect.signature``, which
1136 works for many more types of callables.
1138 Many builtins in the standard library are also supported.
1139 """
1141from . import _signatures as _sigs