functoolz.pyx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. import inspect
  2. import sys
  3. from functools import partial
  4. from operator import attrgetter
  5. from textwrap import dedent
  6. from types import MethodType
  7. from cytoolz.utils import no_default
  8. from cytoolz.compatibility import PY3, PY34, filter as ifilter, map as imap, reduce, import_module
  9. import cytoolz._signatures as _sigs
  10. from toolz.functoolz import (InstanceProperty, instanceproperty, is_arity,
  11. num_required_args, has_varargs, has_keywords,
  12. is_valid_args, is_partial_args)
  13. cimport cython
  14. from cpython.dict cimport PyDict_Merge, PyDict_New
  15. from cpython.object cimport (PyCallable_Check, PyObject_Call, PyObject_CallObject,
  16. PyObject_RichCompare, Py_EQ, Py_NE)
  17. from cpython.ref cimport PyObject
  18. from cpython.sequence cimport PySequence_Concat
  19. from cpython.set cimport PyFrozenSet_New
  20. from cpython.tuple cimport PyTuple_Check, PyTuple_GET_SIZE
  21. __all__ = ['identity', 'thread_first', 'thread_last', 'memoize', 'compose', 'compose_left',
  22. 'pipe', 'complement', 'juxt', 'do', 'curry', 'memoize', 'flip',
  23. 'excepts', 'apply']
  24. cpdef object identity(object x):
  25. return x
  26. def apply(*func_and_args, **kwargs):
  27. """
  28. Applies a function and returns the results
  29. >>> def double(x): return 2*x
  30. >>> def inc(x): return x + 1
  31. >>> apply(double, 5)
  32. 10
  33. >>> tuple(map(apply, [double, inc, double], [10, 500, 8000]))
  34. (20, 501, 16000)
  35. """
  36. if not func_and_args:
  37. raise TypeError('func argument is required')
  38. return func_and_args[0](*func_and_args[1:], **kwargs)
  39. cdef object c_thread_first(object val, object forms):
  40. cdef object form, func
  41. cdef tuple args
  42. for form in forms:
  43. if PyCallable_Check(form):
  44. val = form(val)
  45. elif PyTuple_Check(form):
  46. func, args = form[0], (val,) + form[1:]
  47. val = PyObject_CallObject(func, args)
  48. else:
  49. val = None
  50. return val
  51. def thread_first(val, *forms):
  52. """
  53. Thread value through a sequence of functions/forms
  54. >>> def double(x): return 2*x
  55. >>> def inc(x): return x + 1
  56. >>> thread_first(1, inc, double)
  57. 4
  58. If the function expects more than one input you can specify those inputs
  59. in a tuple. The value is used as the first input.
  60. >>> def add(x, y): return x + y
  61. >>> def pow(x, y): return x**y
  62. >>> thread_first(1, (add, 4), (pow, 2)) # pow(add(1, 4), 2)
  63. 25
  64. So in general
  65. thread_first(x, f, (g, y, z))
  66. expands to
  67. g(f(x), y, z)
  68. See Also:
  69. thread_last
  70. """
  71. return c_thread_first(val, forms)
  72. cdef object c_thread_last(object val, object forms):
  73. cdef object form, func
  74. cdef tuple args
  75. for form in forms:
  76. if PyCallable_Check(form):
  77. val = form(val)
  78. elif PyTuple_Check(form):
  79. func, args = form[0], form[1:] + (val,)
  80. val = PyObject_CallObject(func, args)
  81. else:
  82. val = None
  83. return val
  84. def thread_last(val, *forms):
  85. """
  86. Thread value through a sequence of functions/forms
  87. >>> def double(x): return 2*x
  88. >>> def inc(x): return x + 1
  89. >>> thread_last(1, inc, double)
  90. 4
  91. If the function expects more than one input you can specify those inputs
  92. in a tuple. The value is used as the last input.
  93. >>> def add(x, y): return x + y
  94. >>> def pow(x, y): return x**y
  95. >>> thread_last(1, (add, 4), (pow, 2)) # pow(2, add(4, 1))
  96. 32
  97. So in general
  98. thread_last(x, f, (g, y, z))
  99. expands to
  100. g(y, z, f(x))
  101. >>> def iseven(x):
  102. ... return x % 2 == 0
  103. >>> list(thread_last([1, 2, 3], (map, inc), (filter, iseven)))
  104. [2, 4]
  105. See Also:
  106. thread_first
  107. """
  108. return c_thread_last(val, forms)
  109. cdef struct partialobject:
  110. PyObject _
  111. PyObject *fn
  112. PyObject *args
  113. PyObject *kw
  114. PyObject *dict
  115. PyObject *weakreflist
  116. cdef object _partial = partial(lambda: None)
  117. cdef object _empty_kwargs():
  118. if <object> (<partialobject*> _partial).kw is None:
  119. return None
  120. return PyDict_New()
  121. cdef class curry:
  122. """ curry(self, *args, **kwargs)
  123. Curry a callable function
  124. Enables partial application of arguments through calling a function with an
  125. incomplete set of arguments.
  126. >>> def mul(x, y):
  127. ... return x * y
  128. >>> mul = curry(mul)
  129. >>> double = mul(2)
  130. >>> double(10)
  131. 20
  132. Also supports keyword arguments
  133. >>> @curry # Can use curry as a decorator
  134. ... def f(x, y, a=10):
  135. ... return a * (x + y)
  136. >>> add = f(a=1)
  137. >>> add(2, 3)
  138. 5
  139. See Also:
  140. cytoolz.curried - namespace of curried functions
  141. https://toolz.readthedocs.io/en/latest/curry.html
  142. """
  143. def __cinit__(self, *args, **kwargs):
  144. if not args:
  145. raise TypeError('__init__() takes at least 2 arguments (1 given)')
  146. func, args = args[0], args[1:]
  147. if not PyCallable_Check(func):
  148. raise TypeError("Input must be callable")
  149. # curry- or functools.partial-like object? Unpack and merge arguments
  150. if (hasattr(func, 'func')
  151. and hasattr(func, 'args')
  152. and hasattr(func, 'keywords')
  153. and isinstance(func.args, tuple)):
  154. if func.keywords:
  155. PyDict_Merge(kwargs, func.keywords, False)
  156. ## Equivalent to:
  157. # for key, val in func.keywords.items():
  158. # if key not in kwargs:
  159. # kwargs[key] = val
  160. args = func.args + args
  161. func = func.func
  162. self.func = func
  163. self.args = args
  164. self.keywords = kwargs if kwargs else _empty_kwargs()
  165. self.__doc__ = getattr(func, '__doc__', None)
  166. self.__name__ = getattr(func, '__name__', '<curry>')
  167. self.__module__ = getattr(func, '__module__', None)
  168. self.__qualname__ = getattr(func, '__qualname__', None)
  169. self._sigspec = None
  170. self._has_unknown_args = None
  171. def __str__(self):
  172. return str(self.func)
  173. def __repr__(self):
  174. return repr(self.func)
  175. def __hash__(self):
  176. return hash((self.func, self.args,
  177. frozenset(self.keywords.items()) if self.keywords
  178. else None))
  179. def __richcmp__(self, other, int op):
  180. is_equal = (isinstance(other, curry) and self.func == other.func and
  181. self.args == other.args and self.keywords == other.keywords)
  182. if op == Py_EQ:
  183. return is_equal
  184. if op == Py_NE:
  185. return not is_equal
  186. return PyObject_RichCompare(id(self), id(other), op)
  187. def __call__(self, *args, **kwargs):
  188. cdef object val
  189. if PyTuple_GET_SIZE(args) == 0:
  190. args = self.args
  191. elif PyTuple_GET_SIZE(self.args) != 0:
  192. args = PySequence_Concat(self.args, args)
  193. if self.keywords is not None:
  194. PyDict_Merge(kwargs, self.keywords, False)
  195. try:
  196. return self.func(*args, **kwargs)
  197. except TypeError as val:
  198. if self._should_curry_internal(args, kwargs, val):
  199. return type(self)(self.func, *args, **kwargs)
  200. raise
  201. def _should_curry(self, args, kwargs, exc=None):
  202. if PyTuple_GET_SIZE(args) == 0:
  203. args = self.args
  204. elif PyTuple_GET_SIZE(self.args) != 0:
  205. args = PySequence_Concat(self.args, args)
  206. if self.keywords is not None:
  207. PyDict_Merge(kwargs, self.keywords, False)
  208. return self._should_curry_internal(args, kwargs)
  209. def _should_curry_internal(self, args, kwargs, exc=None):
  210. func = self.func
  211. # `toolz` has these three lines
  212. #args = self.args + args
  213. #if self.keywords:
  214. # kwargs = dict(self.keywords, **kwargs)
  215. if self._sigspec is None:
  216. sigspec = self._sigspec = _sigs.signature_or_spec(func)
  217. self._has_unknown_args = has_varargs(func, sigspec) is not False
  218. else:
  219. sigspec = self._sigspec
  220. if is_partial_args(func, args, kwargs, sigspec) is False:
  221. # Nothing can make the call valid
  222. return False
  223. elif self._has_unknown_args:
  224. # The call may be valid and raised a TypeError, but we curry
  225. # anyway because the function may have `*args`. This is useful
  226. # for decorators with signature `func(*args, **kwargs)`.
  227. return True
  228. elif not is_valid_args(func, args, kwargs, sigspec):
  229. # Adding more arguments may make the call valid
  230. return True
  231. else:
  232. # There was a genuine TypeError
  233. return False
  234. def bind(self, *args, **kwargs):
  235. return type(self)(self, *args, **kwargs)
  236. def call(self, *args, **kwargs):
  237. cdef object val
  238. if PyTuple_GET_SIZE(args) == 0:
  239. args = self.args
  240. elif PyTuple_GET_SIZE(self.args) != 0:
  241. args = PySequence_Concat(self.args, args)
  242. if self.keywords is not None:
  243. PyDict_Merge(kwargs, self.keywords, False)
  244. return self.func(*args, **kwargs)
  245. def __get__(self, instance, owner):
  246. if instance is None:
  247. return self
  248. return type(self)(self, instance)
  249. property __signature__:
  250. def __get__(self):
  251. sig = inspect.signature(self.func)
  252. args = self.args or ()
  253. keywords = self.keywords or {}
  254. if is_partial_args(self.func, args, keywords, sig) is False:
  255. raise TypeError('curry object has incorrect arguments')
  256. params = list(sig.parameters.values())
  257. skip = 0
  258. for param in params[:len(args)]:
  259. if param.kind == param.VAR_POSITIONAL:
  260. break
  261. skip += 1
  262. kwonly = False
  263. newparams = []
  264. for param in params[skip:]:
  265. kind = param.kind
  266. default = param.default
  267. if kind == param.VAR_KEYWORD:
  268. pass
  269. elif kind == param.VAR_POSITIONAL:
  270. if kwonly:
  271. continue
  272. elif param.name in keywords:
  273. default = keywords[param.name]
  274. kind = param.KEYWORD_ONLY
  275. kwonly = True
  276. else:
  277. if kwonly:
  278. kind = param.KEYWORD_ONLY
  279. if default is param.empty:
  280. default = no_default
  281. newparams.append(param.replace(default=default, kind=kind))
  282. return sig.replace(parameters=newparams)
  283. def __reduce__(self):
  284. func = self.func
  285. modname = getattr(func, '__module__', None)
  286. qualname = getattr(func, '__qualname__', None)
  287. if qualname is None:
  288. qualname = getattr(func, '__name__', None)
  289. is_decorated = None
  290. if modname and qualname:
  291. attrs = []
  292. obj = import_module(modname)
  293. for attr in qualname.split('.'):
  294. if isinstance(obj, curry):
  295. attrs.append('func')
  296. obj = obj.func
  297. obj = getattr(obj, attr, None)
  298. if obj is None:
  299. break
  300. attrs.append(attr)
  301. if isinstance(obj, curry) and obj.func is func:
  302. is_decorated = obj is self
  303. qualname = '.'.join(attrs)
  304. func = '%s:%s' % (modname, qualname)
  305. state = (type(self), func, self.args, self.keywords, is_decorated)
  306. return (_restore_curry, state)
  307. cpdef object _restore_curry(cls, func, args, kwargs, is_decorated):
  308. if isinstance(func, str):
  309. modname, qualname = func.rsplit(':', 1)
  310. obj = import_module(modname)
  311. for attr in qualname.split('.'):
  312. obj = getattr(obj, attr)
  313. if is_decorated:
  314. return obj
  315. func = obj.func
  316. obj = cls(func, *args, **(kwargs or {}))
  317. return obj
  318. cpdef object memoize(object func, object cache=None, object key=None):
  319. """
  320. Cache a function's result for speedy future evaluation
  321. Considerations:
  322. Trades memory for speed.
  323. Only use on pure functions.
  324. >>> def add(x, y): return x + y
  325. >>> add = memoize(add)
  326. Or use as a decorator
  327. >>> @memoize
  328. ... def add(x, y):
  329. ... return x + y
  330. Use the ``cache`` keyword to provide a dict-like object as an initial cache
  331. >>> @memoize(cache={(1, 2): 3})
  332. ... def add(x, y):
  333. ... return x + y
  334. Note that the above works as a decorator because ``memoize`` is curried.
  335. It is also possible to provide a ``key(args, kwargs)`` function that
  336. calculates keys used for the cache, which receives an ``args`` tuple and
  337. ``kwargs`` dict as input, and must return a hashable value. However,
  338. the default key function should be sufficient most of the time.
  339. >>> # Use key function that ignores extraneous keyword arguments
  340. >>> @memoize(key=lambda args, kwargs: args)
  341. ... def add(x, y, verbose=False):
  342. ... if verbose:
  343. ... print('Calculating %s + %s' % (x, y))
  344. ... return x + y
  345. """
  346. return _memoize(func, cache, key)
  347. cdef class _memoize:
  348. property __doc__:
  349. def __get__(self):
  350. return self.func.__doc__
  351. property __name__:
  352. def __get__(self):
  353. return self.func.__name__
  354. property __wrapped__:
  355. def __get__(self):
  356. return self.func
  357. def __cinit__(self, func, cache, key):
  358. self.func = func
  359. if cache is None:
  360. self.cache = PyDict_New()
  361. else:
  362. self.cache = cache
  363. self.key = key
  364. try:
  365. self.may_have_kwargs = has_keywords(func) is not False
  366. # Is unary function (single arg, no variadic argument or keywords)?
  367. self.is_unary = is_arity(1, func)
  368. except TypeError:
  369. self.is_unary = False
  370. self.may_have_kwargs = True
  371. def __call__(self, *args, **kwargs):
  372. cdef object key
  373. if self.key is not None:
  374. key = self.key(args, kwargs)
  375. elif self.is_unary:
  376. key = args[0]
  377. elif self.may_have_kwargs:
  378. key = (args or None,
  379. PyFrozenSet_New(kwargs.items()) if kwargs else None)
  380. else:
  381. key = args
  382. if key in self.cache:
  383. return self.cache[key]
  384. else:
  385. result = PyObject_Call(self.func, args, kwargs)
  386. self.cache[key] = result
  387. return result
  388. def __get__(self, instance, owner):
  389. if instance is None:
  390. return self
  391. return curry(self, instance)
  392. cdef class Compose:
  393. """ Compose(self, *funcs)
  394. A composition of functions
  395. See Also:
  396. compose
  397. """
  398. # fix for #103, note: we cannot use __name__ at module-scope in cython
  399. __module__ = 'cytooz.functoolz'
  400. def __cinit__(self, *funcs):
  401. self.first = funcs[-1]
  402. self.funcs = tuple(reversed(funcs[:-1]))
  403. def __call__(self, *args, **kwargs):
  404. cdef object func, ret
  405. ret = PyObject_Call(self.first, args, kwargs)
  406. for func in self.funcs:
  407. ret = func(ret)
  408. return ret
  409. def __reduce__(self):
  410. return (Compose, (self.first,), self.funcs)
  411. def __setstate__(self, state):
  412. self.funcs = state
  413. def __repr__(self):
  414. return '{.__class__.__name__}{!r}'.format(
  415. self, tuple(reversed((self.first, ) + self.funcs)))
  416. def __eq__(self, other):
  417. if isinstance(other, Compose):
  418. return other.first == self.first and other.funcs == self.funcs
  419. return NotImplemented
  420. def __ne__(self, other):
  421. if isinstance(other, Compose):
  422. return other.first != self.first or other.funcs != self.funcs
  423. return NotImplemented
  424. def __hash__(self):
  425. return hash(self.first) ^ hash(self.funcs)
  426. def __get__(self, obj, objtype):
  427. if obj is None:
  428. return self
  429. elif PY3:
  430. return MethodType(self, obj)
  431. else:
  432. return MethodType(self, obj, objtype)
  433. property __wrapped__:
  434. def __get__(self):
  435. return self.first
  436. property __signature__:
  437. def __get__(self):
  438. base = inspect.signature(self.first)
  439. last = inspect.signature(self.funcs[-1])
  440. return base.replace(return_annotation=last.return_annotation)
  441. property __name__:
  442. def __get__(self):
  443. try:
  444. return '_of_'.join(
  445. f.__name__ for f in reversed((self.first,) + self.funcs)
  446. )
  447. except AttributeError:
  448. return type(self).__name__
  449. property __doc__:
  450. def __get__(self):
  451. def composed_doc(*fs):
  452. """Generate a docstring for the composition of fs.
  453. """
  454. if not fs:
  455. # Argument name for the docstring.
  456. return '*args, **kwargs'
  457. return '{f}({g})'.format(f=fs[0].__name__, g=composed_doc(*fs[1:]))
  458. try:
  459. return (
  460. 'lambda *args, **kwargs: ' +
  461. composed_doc(*reversed((self.first,) + self.funcs))
  462. )
  463. except AttributeError:
  464. # One of our callables does not have a `__name__`, whatever.
  465. return 'A composition of functions'
  466. cdef object c_compose(object funcs):
  467. if not funcs:
  468. return identity
  469. elif len(funcs) == 1:
  470. return funcs[0]
  471. else:
  472. return Compose(*funcs)
  473. def compose(*funcs):
  474. """
  475. Compose functions to operate in series.
  476. Returns a function that applies other functions in sequence.
  477. Functions are applied from right to left so that
  478. ``compose(f, g, h)(x, y)`` is the same as ``f(g(h(x, y)))``.
  479. If no arguments are provided, the identity function (f(x) = x) is returned.
  480. >>> inc = lambda i: i + 1
  481. >>> compose(str, inc)(3)
  482. '4'
  483. See Also:
  484. compose_left
  485. pipe
  486. """
  487. return c_compose(funcs)
  488. cdef object c_compose_left(object funcs):
  489. if not funcs:
  490. return identity
  491. elif len(funcs) == 1:
  492. return funcs[0]
  493. else:
  494. return Compose(*reversed(funcs))
  495. def compose_left(*funcs):
  496. """
  497. Compose functions to operate in series.
  498. Returns a function that applies other functions in sequence.
  499. Functions are applied from left to right so that
  500. ``compose_left(f, g, h)(x, y)`` is the same as ``h(g(f(x, y)))``.
  501. If no arguments are provided, the identity function (f(x) = x) is returned.
  502. >>> inc = lambda i: i + 1
  503. >>> compose_left(inc, str)(3)
  504. '4'
  505. See Also:
  506. compose
  507. pipe
  508. """
  509. return c_compose_left(funcs)
  510. cdef object c_pipe(object data, object funcs):
  511. cdef object func
  512. for func in funcs:
  513. data = func(data)
  514. return data
  515. def pipe(data, *funcs):
  516. """
  517. Pipe a value through a sequence of functions
  518. I.e. ``pipe(data, f, g, h)`` is equivalent to ``h(g(f(data)))``
  519. We think of the value as progressing through a pipe of several
  520. transformations, much like pipes in UNIX
  521. ``$ cat data | f | g | h``
  522. >>> double = lambda i: 2 * i
  523. >>> pipe(3, double, str)
  524. '6'
  525. See Also:
  526. compose
  527. compose_left
  528. thread_first
  529. thread_last
  530. """
  531. return c_pipe(data, funcs)
  532. cdef class complement:
  533. """ complement(func)
  534. Convert a predicate function to its logical complement.
  535. In other words, return a function that, for inputs that normally
  536. yield True, yields False, and vice-versa.
  537. >>> def iseven(n): return n % 2 == 0
  538. >>> isodd = complement(iseven)
  539. >>> iseven(2)
  540. True
  541. >>> isodd(2)
  542. False
  543. """
  544. def __cinit__(self, func):
  545. self.func = func
  546. def __call__(self, *args, **kwargs):
  547. return not PyObject_Call(self.func, args, kwargs) # use PyObject_Not?
  548. def __reduce__(self):
  549. return (complement, (self.func,))
  550. cdef class _juxt_inner:
  551. def __cinit__(self, funcs):
  552. self.funcs = tuple(funcs)
  553. def __call__(self, *args, **kwargs):
  554. if kwargs:
  555. return tuple(PyObject_Call(func, args, kwargs) for func in self.funcs)
  556. else:
  557. return tuple(PyObject_CallObject(func, args) for func in self.funcs)
  558. def __reduce__(self):
  559. return (_juxt_inner, (self.funcs,))
  560. cdef object c_juxt(object funcs):
  561. return _juxt_inner(funcs)
  562. def juxt(*funcs):
  563. """
  564. Creates a function that calls several functions with the same arguments
  565. Takes several functions and returns a function that applies its arguments
  566. to each of those functions then returns a tuple of the results.
  567. Name comes from juxtaposition: the fact of two things being seen or placed
  568. close together with contrasting effect.
  569. >>> inc = lambda x: x + 1
  570. >>> double = lambda x: x * 2
  571. >>> juxt(inc, double)(10)
  572. (11, 20)
  573. >>> juxt([inc, double])(10)
  574. (11, 20)
  575. """
  576. if len(funcs) == 1 and not PyCallable_Check(funcs[0]):
  577. funcs = funcs[0]
  578. return c_juxt(funcs)
  579. cpdef object do(object func, object x):
  580. """
  581. Runs ``func`` on ``x``, returns ``x``
  582. Because the results of ``func`` are not returned, only the side
  583. effects of ``func`` are relevant.
  584. Logging functions can be made by composing ``do`` with a storage function
  585. like ``list.append`` or ``file.write``
  586. >>> from cytoolz import compose
  587. >>> from cytoolz.curried import do
  588. >>> log = []
  589. >>> inc = lambda x: x + 1
  590. >>> inc = compose(inc, do(log.append))
  591. >>> inc(1)
  592. 2
  593. >>> inc(11)
  594. 12
  595. >>> log
  596. [1, 11]
  597. """
  598. func(x)
  599. return x
  600. cpdef object flip(object func, object a, object b):
  601. """
  602. Call the function call with the arguments flipped
  603. This function is curried.
  604. >>> def div(a, b):
  605. ... return a // b
  606. ...
  607. >>> flip(div, 2, 6)
  608. 3
  609. >>> div_by_two = flip(div, 2)
  610. >>> div_by_two(4)
  611. 2
  612. This is particularly useful for built in functions and functions defined
  613. in C extensions that accept positional only arguments. For example:
  614. isinstance, issubclass.
  615. >>> data = [1, 'a', 'b', 2, 1.5, object(), 3]
  616. >>> only_ints = list(filter(flip(isinstance, int), data))
  617. >>> only_ints
  618. [1, 2, 3]
  619. """
  620. return PyObject_CallObject(func, (b, a))
  621. _flip = flip # uncurried
  622. cpdef object return_none(object exc):
  623. """
  624. Returns None.
  625. """
  626. return None
  627. cdef class excepts:
  628. """
  629. A wrapper around a function to catch exceptions and
  630. dispatch to a handler.
  631. This is like a functional try/except block, in the same way that
  632. ifexprs are functional if/else blocks.
  633. Examples
  634. --------
  635. >>> excepting = excepts(
  636. ... ValueError,
  637. ... lambda a: [1, 2].index(a),
  638. ... lambda _: -1,
  639. ... )
  640. >>> excepting(1)
  641. 0
  642. >>> excepting(3)
  643. -1
  644. Multiple exceptions and default except clause.
  645. >>> excepting = excepts((IndexError, KeyError), lambda a: a[0])
  646. >>> excepting([])
  647. >>> excepting([1])
  648. 1
  649. >>> excepting({})
  650. >>> excepting({0: 1})
  651. 1
  652. """
  653. def __init__(self, exc, func, handler=return_none):
  654. self.exc = exc
  655. self.func = func
  656. self.handler = handler
  657. def __call__(self, *args, **kwargs):
  658. try:
  659. return self.func(*args, **kwargs)
  660. except self.exc as e:
  661. return self.handler(e)
  662. property __name__:
  663. def __get__(self):
  664. exc = self.exc
  665. try:
  666. if isinstance(exc, tuple):
  667. exc_name = '_or_'.join(map(attrgetter('__name__'), exc))
  668. else:
  669. exc_name = exc.__name__
  670. return '%s_excepting_%s' % (self.func.__name__, exc_name)
  671. except AttributeError:
  672. return 'excepting'
  673. property __doc__:
  674. def __get__(self):
  675. exc = self.exc
  676. try:
  677. if isinstance(exc, tuple):
  678. exc_name = '(%s)' % ', '.join(
  679. map(attrgetter('__name__'), exc),
  680. )
  681. else:
  682. exc_name = exc.__name__
  683. return dedent(
  684. """\
  685. A wrapper around {inst.func.__name__!r} that will except:
  686. {exc}
  687. and handle any exceptions with {inst.handler.__name__!r}.
  688. Docs for {inst.func.__name__!r}:
  689. {inst.func.__doc__}
  690. Docs for {inst.handler.__name__!r}:
  691. {inst.handler.__doc__}
  692. """
  693. ).format(
  694. inst=self,
  695. exc=exc_name,
  696. )
  697. except AttributeError:
  698. return type(self).__doc__