functoolz.pyx 25 KB

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