import inspect
|
|
import sys
|
|
from functools import partial
|
|
from operator import attrgetter
|
|
from textwrap import dedent
|
|
from cytoolz.utils import no_default
|
|
from cytoolz.compatibility import PY3, PY33, PY34, filter as ifilter, map as imap, reduce, import_module
|
|
import cytoolz._signatures as _sigs
|
|
|
|
from toolz.functoolz import (InstanceProperty, instanceproperty, is_arity,
|
|
num_required_args, has_varargs, has_keywords,
|
|
is_valid_args, is_partial_args)
|
|
|
|
cimport cython
|
|
from cpython.dict cimport PyDict_Merge, PyDict_New
|
|
from cpython.object cimport (PyCallable_Check, PyObject_Call, PyObject_CallObject,
|
|
PyObject_RichCompare, Py_EQ, Py_NE)
|
|
from cpython.ref cimport PyObject
|
|
from cpython.sequence cimport PySequence_Concat
|
|
from cpython.set cimport PyFrozenSet_New
|
|
from cpython.tuple cimport PyTuple_Check, PyTuple_GET_SIZE
|
|
|
|
|
|
__all__ = ['identity', 'thread_first', 'thread_last', 'memoize', 'compose',
|
|
'pipe', 'complement', 'juxt', 'do', 'curry', 'memoize', 'flip',
|
|
'excepts']
|
|
|
|
|
|
cpdef object identity(object x):
|
|
return x
|
|
|
|
|
|
cdef object c_thread_first(object val, object forms):
|
|
cdef object form, func
|
|
cdef tuple args
|
|
for form in forms:
|
|
if PyCallable_Check(form):
|
|
val = form(val)
|
|
elif PyTuple_Check(form):
|
|
func, args = form[0], (val,) + form[1:]
|
|
val = PyObject_CallObject(func, args)
|
|
else:
|
|
val = None
|
|
return val
|
|
|
|
|
|
def thread_first(val, *forms):
|
|
"""
|
|
Thread value through a sequence of functions/forms
|
|
|
|
>>> def double(x): return 2*x
|
|
>>> def inc(x): return x + 1
|
|
>>> thread_first(1, inc, double)
|
|
4
|
|
|
|
If the function expects more than one input you can specify those inputs
|
|
in a tuple. The value is used as the first input.
|
|
|
|
>>> def add(x, y): return x + y
|
|
>>> def pow(x, y): return x**y
|
|
>>> thread_first(1, (add, 4), (pow, 2)) # pow(add(1, 4), 2)
|
|
25
|
|
|
|
So in general
|
|
thread_first(x, f, (g, y, z))
|
|
expands to
|
|
g(f(x), y, z)
|
|
|
|
See Also:
|
|
thread_last
|
|
"""
|
|
return c_thread_first(val, forms)
|
|
|
|
|
|
cdef object c_thread_last(object val, object forms):
|
|
cdef object form, func
|
|
cdef tuple args
|
|
for form in forms:
|
|
if PyCallable_Check(form):
|
|
val = form(val)
|
|
elif PyTuple_Check(form):
|
|
func, args = form[0], form[1:] + (val,)
|
|
val = PyObject_CallObject(func, args)
|
|
else:
|
|
val = None
|
|
return val
|
|
|
|
|
|
def thread_last(val, *forms):
|
|
"""
|
|
Thread value through a sequence of functions/forms
|
|
|
|
>>> def double(x): return 2*x
|
|
>>> def inc(x): return x + 1
|
|
>>> thread_last(1, inc, double)
|
|
4
|
|
|
|
If the function expects more than one input you can specify those inputs
|
|
in a tuple. The value is used as the last input.
|
|
|
|
>>> def add(x, y): return x + y
|
|
>>> def pow(x, y): return x**y
|
|
>>> thread_last(1, (add, 4), (pow, 2)) # pow(2, add(4, 1))
|
|
32
|
|
|
|
So in general
|
|
thread_last(x, f, (g, y, z))
|
|
expands to
|
|
g(y, z, f(x))
|
|
|
|
>>> def iseven(x):
|
|
... return x % 2 == 0
|
|
>>> list(thread_last([1, 2, 3], (map, inc), (filter, iseven)))
|
|
[2, 4]
|
|
|
|
See Also:
|
|
thread_first
|
|
"""
|
|
return c_thread_last(val, forms)
|
|
|
|
|
|
cdef struct partialobject:
|
|
PyObject _
|
|
PyObject *fn
|
|
PyObject *args
|
|
PyObject *kw
|
|
PyObject *dict
|
|
PyObject *weakreflist
|
|
|
|
|
|
cdef object _partial = partial(lambda: None)
|
|
|
|
|
|
cdef object _empty_kwargs():
|
|
if <object> (<partialobject*> _partial).kw is None:
|
|
return None
|
|
return PyDict_New()
|
|
|
|
|
|
cdef class curry:
|
|
""" curry(self, *args, **kwargs)
|
|
|
|
Curry a callable function
|
|
|
|
Enables partial application of arguments through calling a function with an
|
|
incomplete set of arguments.
|
|
|
|
>>> def mul(x, y):
|
|
... return x * y
|
|
>>> mul = curry(mul)
|
|
|
|
>>> double = mul(2)
|
|
>>> double(10)
|
|
20
|
|
|
|
Also supports keyword arguments
|
|
|
|
>>> @curry # Can use curry as a decorator
|
|
... def f(x, y, a=10):
|
|
... return a * (x + y)
|
|
|
|
>>> add = f(a=1)
|
|
>>> add(2, 3)
|
|
5
|
|
|
|
See Also:
|
|
cytoolz.curried - namespace of curried functions
|
|
https://toolz.readthedocs.io/en/latest/curry.html
|
|
"""
|
|
|
|
def __cinit__(self, *args, **kwargs):
|
|
if not args:
|
|
raise TypeError('__init__() takes at least 2 arguments (1 given)')
|
|
func, args = args[0], args[1:]
|
|
if not PyCallable_Check(func):
|
|
raise TypeError("Input must be callable")
|
|
|
|
# curry- or functools.partial-like object? Unpack and merge arguments
|
|
if (hasattr(func, 'func')
|
|
and hasattr(func, 'args')
|
|
and hasattr(func, 'keywords')
|
|
and isinstance(func.args, tuple)):
|
|
if func.keywords:
|
|
PyDict_Merge(kwargs, func.keywords, False)
|
|
## Equivalent to:
|
|
# for key, val in func.keywords.items():
|
|
# if key not in kwargs:
|
|
# kwargs[key] = val
|
|
args = func.args + args
|
|
func = func.func
|
|
|
|
self.func = func
|
|
self.args = args
|
|
self.keywords = kwargs if kwargs else _empty_kwargs()
|
|
self.__doc__ = getattr(func, '__doc__', None)
|
|
self.__name__ = getattr(func, '__name__', '<curry>')
|
|
self.__module__ = getattr(func, '__module__', None)
|
|
self.__qualname__ = getattr(func, '__qualname__', None)
|
|
self._sigspec = None
|
|
self._has_unknown_args = None
|
|
|
|
def __str__(self):
|
|
return str(self.func)
|
|
|
|
def __repr__(self):
|
|
return repr(self.func)
|
|
|
|
def __hash__(self):
|
|
return hash((self.func, self.args,
|
|
frozenset(self.keywords.items()) if self.keywords
|
|
else None))
|
|
|
|
def __richcmp__(self, other, int op):
|
|
is_equal = (isinstance(other, curry) and self.func == other.func and
|
|
self.args == other.args and self.keywords == other.keywords)
|
|
if op == Py_EQ:
|
|
return is_equal
|
|
if op == Py_NE:
|
|
return not is_equal
|
|
return PyObject_RichCompare(id(self), id(other), op)
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
cdef object val
|
|
|
|
if PyTuple_GET_SIZE(args) == 0:
|
|
args = self.args
|
|
elif PyTuple_GET_SIZE(self.args) != 0:
|
|
args = PySequence_Concat(self.args, args)
|
|
if self.keywords is not None:
|
|
PyDict_Merge(kwargs, self.keywords, False)
|
|
try:
|
|
return self.func(*args, **kwargs)
|
|
except TypeError as val:
|
|
if self._should_curry_internal(args, kwargs, val):
|
|
return type(self)(self.func, *args, **kwargs)
|
|
raise
|
|
|
|
def _should_curry(self, args, kwargs, exc=None):
|
|
if PyTuple_GET_SIZE(args) == 0:
|
|
args = self.args
|
|
elif PyTuple_GET_SIZE(self.args) != 0:
|
|
args = PySequence_Concat(self.args, args)
|
|
if self.keywords is not None:
|
|
PyDict_Merge(kwargs, self.keywords, False)
|
|
return self._should_curry_internal(args, kwargs)
|
|
|
|
def _should_curry_internal(self, args, kwargs, exc=None):
|
|
func = self.func
|
|
|
|
# `toolz` has these three lines
|
|
#args = self.args + args
|
|
#if self.keywords:
|
|
# kwargs = dict(self.keywords, **kwargs)
|
|
|
|
if self._sigspec is None:
|
|
sigspec = self._sigspec = _sigs.signature_or_spec(func)
|
|
self._has_unknown_args = has_varargs(func, sigspec) is not False
|
|
else:
|
|
sigspec = self._sigspec
|
|
|
|
if is_partial_args(func, args, kwargs, sigspec) is False:
|
|
# Nothing can make the call valid
|
|
return False
|
|
elif self._has_unknown_args:
|
|
# The call may be valid and raised a TypeError, but we curry
|
|
# anyway because the function may have `*args`. This is useful
|
|
# for decorators with signature `func(*args, **kwargs)`.
|
|
return True
|
|
elif not is_valid_args(func, args, kwargs, sigspec):
|
|
# Adding more arguments may make the call valid
|
|
return True
|
|
else:
|
|
# There was a genuine TypeError
|
|
return False
|
|
|
|
def bind(self, *args, **kwargs):
|
|
return type(self)(self, *args, **kwargs)
|
|
|
|
def call(self, *args, **kwargs):
|
|
cdef object val
|
|
|
|
if PyTuple_GET_SIZE(args) == 0:
|
|
args = self.args
|
|
elif PyTuple_GET_SIZE(self.args) != 0:
|
|
args = PySequence_Concat(self.args, args)
|
|
if self.keywords is not None:
|
|
PyDict_Merge(kwargs, self.keywords, False)
|
|
return self.func(*args, **kwargs)
|
|
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
return self
|
|
return type(self)(self, instance)
|
|
|
|
property __signature__:
|
|
def __get__(self):
|
|
try:
|
|
sig = inspect.signature(self.func)
|
|
except TypeError:
|
|
if PY33 and (getattr(self.func, '__module__') or '').startswith('cytoolz.'):
|
|
raise ValueError('callable %r is not supported by signature' % self.func)
|
|
raise
|
|
|
|
args = self.args or ()
|
|
keywords = self.keywords or {}
|
|
if is_partial_args(self.func, args, keywords, sig) is False:
|
|
raise TypeError('curry object has incorrect arguments')
|
|
|
|
params = list(sig.parameters.values())
|
|
skip = 0
|
|
for param in params[:len(args)]:
|
|
if param.kind == param.VAR_POSITIONAL:
|
|
break
|
|
skip += 1
|
|
|
|
kwonly = False
|
|
newparams = []
|
|
for param in params[skip:]:
|
|
kind = param.kind
|
|
default = param.default
|
|
if kind == param.VAR_KEYWORD:
|
|
pass
|
|
elif kind == param.VAR_POSITIONAL:
|
|
if kwonly:
|
|
continue
|
|
elif param.name in keywords:
|
|
default = keywords[param.name]
|
|
kind = param.KEYWORD_ONLY
|
|
kwonly = True
|
|
else:
|
|
if kwonly:
|
|
kind = param.KEYWORD_ONLY
|
|
if default is param.empty:
|
|
default = no_default
|
|
newparams.append(param.replace(default=default, kind=kind))
|
|
|
|
return sig.replace(parameters=newparams)
|
|
|
|
def __reduce__(self):
|
|
func = self.func
|
|
modname = getattr(func, '__module__', None)
|
|
qualname = getattr(func, '__qualname__', None)
|
|
if qualname is None:
|
|
qualname = getattr(func, '__name__', None)
|
|
is_decorated = None
|
|
if modname and qualname:
|
|
attrs = []
|
|
obj = import_module(modname)
|
|
for attr in qualname.split('.'):
|
|
if isinstance(obj, curry):
|
|
attrs.append('func')
|
|
obj = obj.func
|
|
obj = getattr(obj, attr, None)
|
|
if obj is None:
|
|
break
|
|
attrs.append(attr)
|
|
if isinstance(obj, curry) and obj.func is func:
|
|
is_decorated = obj is self
|
|
qualname = '.'.join(attrs)
|
|
func = '%s:%s' % (modname, qualname)
|
|
|
|
state = (type(self), func, self.args, self.keywords, is_decorated)
|
|
return (_restore_curry, state)
|
|
|
|
|
|
cpdef object _restore_curry(cls, func, args, kwargs, is_decorated):
|
|
if isinstance(func, str):
|
|
modname, qualname = func.rsplit(':', 1)
|
|
obj = import_module(modname)
|
|
for attr in qualname.split('.'):
|
|
obj = getattr(obj, attr)
|
|
if is_decorated:
|
|
return obj
|
|
func = obj.func
|
|
obj = cls(func, *args, **(kwargs or {}))
|
|
return obj
|
|
|
|
|
|
cpdef object memoize(object func, object cache=None, object key=None):
|
|
"""
|
|
Cache a function's result for speedy future evaluation
|
|
|
|
Considerations:
|
|
Trades memory for speed.
|
|
Only use on pure functions.
|
|
|
|
>>> def add(x, y): return x + y
|
|
>>> add = memoize(add)
|
|
|
|
Or use as a decorator
|
|
|
|
>>> @memoize
|
|
... def add(x, y):
|
|
... return x + y
|
|
|
|
Use the ``cache`` keyword to provide a dict-like object as an initial cache
|
|
|
|
>>> @memoize(cache={(1, 2): 3})
|
|
... def add(x, y):
|
|
... return x + y
|
|
|
|
Note that the above works as a decorator because ``memoize`` is curried.
|
|
|
|
It is also possible to provide a ``key(args, kwargs)`` function that
|
|
calculates keys used for the cache, which receives an ``args`` tuple and
|
|
``kwargs`` dict as input, and must return a hashable value. However,
|
|
the default key function should be sufficient most of the time.
|
|
|
|
>>> # Use key function that ignores extraneous keyword arguments
|
|
>>> @memoize(key=lambda args, kwargs: args)
|
|
... def add(x, y, verbose=False):
|
|
... if verbose:
|
|
... print('Calculating %s + %s' % (x, y))
|
|
... return x + y
|
|
"""
|
|
return _memoize(func, cache, key)
|
|
|
|
|
|
cdef class _memoize:
|
|
|
|
property __doc__:
|
|
def __get__(self):
|
|
return self.func.__doc__
|
|
|
|
property __name__:
|
|
def __get__(self):
|
|
return self.func.__name__
|
|
|
|
property __wrapped__:
|
|
def __get__(self):
|
|
return self.func
|
|
|
|
def __cinit__(self, func, cache, key):
|
|
self.func = func
|
|
if cache is None:
|
|
self.cache = PyDict_New()
|
|
else:
|
|
self.cache = cache
|
|
self.key = key
|
|
|
|
try:
|
|
self.may_have_kwargs = has_keywords(func) is not False
|
|
# Is unary function (single arg, no variadic argument or keywords)?
|
|
self.is_unary = is_arity(1, func)
|
|
except TypeError:
|
|
self.is_unary = False
|
|
self.may_have_kwargs = True
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
cdef object key
|
|
if self.key is not None:
|
|
key = self.key(args, kwargs)
|
|
elif self.is_unary:
|
|
key = args[0]
|
|
elif self.may_have_kwargs:
|
|
key = (args or None,
|
|
PyFrozenSet_New(kwargs.items()) if kwargs else None)
|
|
else:
|
|
key = args
|
|
|
|
if key in self.cache:
|
|
return self.cache[key]
|
|
else:
|
|
result = PyObject_Call(self.func, args, kwargs)
|
|
self.cache[key] = result
|
|
return result
|
|
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
return self
|
|
return curry(self, instance)
|
|
|
|
|
|
cdef class Compose:
|
|
""" Compose(self, *funcs)
|
|
|
|
A composition of functions
|
|
|
|
See Also:
|
|
compose
|
|
"""
|
|
# fix for #103, note: we cannot use __name__ at module-scope in cython
|
|
__module__ = 'cytooz.functoolz'
|
|
|
|
def __cinit__(self, *funcs):
|
|
self.first = funcs[-1]
|
|
self.funcs = tuple(reversed(funcs[:-1]))
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
cdef object func, ret
|
|
ret = PyObject_Call(self.first, args, kwargs)
|
|
for func in self.funcs:
|
|
ret = func(ret)
|
|
return ret
|
|
|
|
def __reduce__(self):
|
|
return (Compose, (self.first,), self.funcs)
|
|
|
|
def __setstate__(self, state):
|
|
self.funcs = state
|
|
|
|
property __name__:
|
|
def __get__(self):
|
|
try:
|
|
return '_of_'.join(
|
|
f.__name__ for f in reversed((self.first,) + self.funcs)
|
|
)
|
|
except AttributeError:
|
|
return type(self).__name__
|
|
|
|
property __doc__:
|
|
def __get__(self):
|
|
def composed_doc(*fs):
|
|
"""Generate a docstring for the composition of fs.
|
|
"""
|
|
if not fs:
|
|
# Argument name for the docstring.
|
|
return '*args, **kwargs'
|
|
|
|
return '{f}({g})'.format(f=fs[0].__name__, g=composed_doc(*fs[1:]))
|
|
|
|
try:
|
|
return (
|
|
'lambda *args, **kwargs: ' +
|
|
composed_doc(*reversed((self.first,) + self.funcs))
|
|
)
|
|
except AttributeError:
|
|
# One of our callables does not have a `__name__`, whatever.
|
|
return 'A composition of functions'
|
|
|
|
|
|
cdef object c_compose(object funcs):
|
|
if not funcs:
|
|
return identity
|
|
elif len(funcs) == 1:
|
|
return funcs[0]
|
|
else:
|
|
return Compose(*funcs)
|
|
|
|
|
|
def compose(*funcs):
|
|
"""
|
|
Compose functions to operate in series.
|
|
|
|
Returns a function that applies other functions in sequence.
|
|
|
|
Functions are applied from right to left so that
|
|
``compose(f, g, h)(x, y)`` is the same as ``f(g(h(x, y)))``.
|
|
|
|
If no arguments are provided, the identity function (f(x) = x) is returned.
|
|
|
|
>>> inc = lambda i: i + 1
|
|
>>> compose(str, inc)(3)
|
|
'4'
|
|
|
|
See Also:
|
|
pipe
|
|
"""
|
|
return c_compose(funcs)
|
|
|
|
|
|
cdef object c_pipe(object data, object funcs):
|
|
cdef object func
|
|
for func in funcs:
|
|
data = func(data)
|
|
return data
|
|
|
|
|
|
def pipe(data, *funcs):
|
|
"""
|
|
Pipe a value through a sequence of functions
|
|
|
|
I.e. ``pipe(data, f, g, h)`` is equivalent to ``h(g(f(data)))``
|
|
|
|
We think of the value as progressing through a pipe of several
|
|
transformations, much like pipes in UNIX
|
|
|
|
``$ cat data | f | g | h``
|
|
|
|
>>> double = lambda i: 2 * i
|
|
>>> pipe(3, double, str)
|
|
'6'
|
|
|
|
See Also:
|
|
compose
|
|
thread_first
|
|
thread_last
|
|
"""
|
|
return c_pipe(data, funcs)
|
|
|
|
|
|
cdef class complement:
|
|
""" complement(func)
|
|
|
|
Convert a predicate function to its logical complement.
|
|
|
|
In other words, return a function that, for inputs that normally
|
|
yield True, yields False, and vice-versa.
|
|
|
|
>>> def iseven(n): return n % 2 == 0
|
|
>>> isodd = complement(iseven)
|
|
>>> iseven(2)
|
|
True
|
|
>>> isodd(2)
|
|
False
|
|
"""
|
|
def __cinit__(self, func):
|
|
self.func = func
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
return not PyObject_Call(self.func, args, kwargs) # use PyObject_Not?
|
|
|
|
def __reduce__(self):
|
|
return (complement, (self.func,))
|
|
|
|
|
|
cdef class _juxt_inner:
|
|
def __cinit__(self, funcs):
|
|
self.funcs = tuple(funcs)
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
if kwargs:
|
|
return tuple(PyObject_Call(func, args, kwargs) for func in self.funcs)
|
|
else:
|
|
return tuple(PyObject_CallObject(func, args) for func in self.funcs)
|
|
|
|
def __reduce__(self):
|
|
return (_juxt_inner, (self.funcs,))
|
|
|
|
|
|
cdef object c_juxt(object funcs):
|
|
return _juxt_inner(funcs)
|
|
|
|
|
|
def juxt(*funcs):
|
|
"""
|
|
Creates a function that calls several functions with the same arguments
|
|
|
|
Takes several functions and returns a function that applies its arguments
|
|
to each of those functions then returns a tuple of the results.
|
|
|
|
Name comes from juxtaposition: the fact of two things being seen or placed
|
|
close together with contrasting effect.
|
|
|
|
>>> inc = lambda x: x + 1
|
|
>>> double = lambda x: x * 2
|
|
>>> juxt(inc, double)(10)
|
|
(11, 20)
|
|
>>> juxt([inc, double])(10)
|
|
(11, 20)
|
|
"""
|
|
if len(funcs) == 1 and not PyCallable_Check(funcs[0]):
|
|
funcs = funcs[0]
|
|
return c_juxt(funcs)
|
|
|
|
|
|
cpdef object do(object func, object x):
|
|
"""
|
|
Runs ``func`` on ``x``, returns ``x``
|
|
|
|
Because the results of ``func`` are not returned, only the side
|
|
effects of ``func`` are relevant.
|
|
|
|
Logging functions can be made by composing ``do`` with a storage function
|
|
like ``list.append`` or ``file.write``
|
|
|
|
>>> from cytoolz import compose
|
|
>>> from cytoolz.curried import do
|
|
|
|
>>> log = []
|
|
>>> inc = lambda x: x + 1
|
|
>>> inc = compose(inc, do(log.append))
|
|
>>> inc(1)
|
|
2
|
|
>>> inc(11)
|
|
12
|
|
>>> log
|
|
[1, 11]
|
|
"""
|
|
func(x)
|
|
return x
|
|
|
|
|
|
cpdef object flip(object func, object a, object b):
|
|
"""
|
|
Call the function call with the arguments flipped
|
|
|
|
This function is curried.
|
|
|
|
>>> def div(a, b):
|
|
... return a // b
|
|
...
|
|
>>> flip(div, 2, 6)
|
|
3
|
|
>>> div_by_two = flip(div, 2)
|
|
>>> div_by_two(4)
|
|
2
|
|
|
|
This is particularly useful for built in functions and functions defined
|
|
in C extensions that accept positional only arguments. For example:
|
|
isinstance, issubclass.
|
|
|
|
>>> data = [1, 'a', 'b', 2, 1.5, object(), 3]
|
|
>>> only_ints = list(filter(flip(isinstance, int), data))
|
|
>>> only_ints
|
|
[1, 2, 3]
|
|
"""
|
|
return PyObject_CallObject(func, (b, a))
|
|
|
|
|
|
_flip = flip # uncurried
|
|
|
|
|
|
cpdef object return_none(object exc):
|
|
"""
|
|
Returns None.
|
|
"""
|
|
return None
|
|
|
|
|
|
cdef class excepts:
|
|
"""
|
|
A wrapper around a function to catch exceptions and
|
|
dispatch to a handler.
|
|
|
|
This is like a functional try/except block, in the same way that
|
|
ifexprs are functional if/else blocks.
|
|
|
|
Examples
|
|
--------
|
|
>>> excepting = excepts(
|
|
... ValueError,
|
|
... lambda a: [1, 2].index(a),
|
|
... lambda _: -1,
|
|
... )
|
|
>>> excepting(1)
|
|
0
|
|
>>> excepting(3)
|
|
-1
|
|
|
|
Multiple exceptions and default except clause.
|
|
>>> excepting = excepts((IndexError, KeyError), lambda a: a[0])
|
|
>>> excepting([])
|
|
>>> excepting([1])
|
|
1
|
|
>>> excepting({})
|
|
>>> excepting({0: 1})
|
|
1
|
|
"""
|
|
|
|
def __init__(self, exc, func, handler=return_none):
|
|
self.exc = exc
|
|
self.func = func
|
|
self.handler = handler
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
try:
|
|
return self.func(*args, **kwargs)
|
|
except self.exc as e:
|
|
return self.handler(e)
|
|
|
|
property __name__:
|
|
def __get__(self):
|
|
exc = self.exc
|
|
try:
|
|
if isinstance(exc, tuple):
|
|
exc_name = '_or_'.join(map(attrgetter('__name__'), exc))
|
|
else:
|
|
exc_name = exc.__name__
|
|
return '%s_excepting_%s' % (self.func.__name__, exc_name)
|
|
except AttributeError:
|
|
return 'excepting'
|
|
|
|
property __doc__:
|
|
def __get__(self):
|
|
exc = self.exc
|
|
try:
|
|
if isinstance(exc, tuple):
|
|
exc_name = '(%s)' % ', '.join(
|
|
map(attrgetter('__name__'), exc),
|
|
)
|
|
else:
|
|
exc_name = exc.__name__
|
|
|
|
return dedent(
|
|
"""\
|
|
A wrapper around {inst.func.__name__!r} that will except:
|
|
{exc}
|
|
and handle any exceptions with {inst.handler.__name__!r}.
|
|
|
|
Docs for {inst.func.__name__!r}:
|
|
{inst.func.__doc__}
|
|
|
|
Docs for {inst.handler.__name__!r}:
|
|
{inst.handler.__doc__}
|
|
"""
|
|
).format(
|
|
inst=self,
|
|
exc=exc_name,
|
|
)
|
|
except AttributeError:
|
|
return type(self).__doc__
|
|
|