test_functoolz.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. import platform
  2. from toolz.functoolz import (thread_first, thread_last, memoize, curry,
  3. compose, pipe, complement, do, juxt, flip, excepts)
  4. from operator import add, mul, itemgetter
  5. from toolz.utils import raises
  6. from functools import partial
  7. def iseven(x):
  8. return x % 2 == 0
  9. def isodd(x):
  10. return x % 2 == 1
  11. def inc(x):
  12. return x + 1
  13. def double(x):
  14. return 2 * x
  15. def test_thread_first():
  16. assert thread_first(2) == 2
  17. assert thread_first(2, inc) == 3
  18. assert thread_first(2, inc, inc) == 4
  19. assert thread_first(2, double, inc) == 5
  20. assert thread_first(2, (add, 5), double) == 14
  21. def test_thread_last():
  22. assert list(thread_last([1, 2, 3], (map, inc), (filter, iseven))) == [2, 4]
  23. assert list(thread_last([1, 2, 3], (map, inc), (filter, isodd))) == [3]
  24. assert thread_last(2, (add, 5), double) == 14
  25. def test_memoize():
  26. fn_calls = [0] # Storage for side effects
  27. def f(x, y):
  28. """ A docstring """
  29. fn_calls[0] += 1
  30. return x + y
  31. mf = memoize(f)
  32. assert mf(2, 3) is mf(2, 3)
  33. assert fn_calls == [1] # function was only called once
  34. assert mf.__doc__ == f.__doc__
  35. assert raises(TypeError, lambda: mf(1, {}))
  36. def test_memoize_kwargs():
  37. fn_calls = [0] # Storage for side effects
  38. def f(x, y=0):
  39. return x + y
  40. mf = memoize(f)
  41. assert mf(1) == f(1)
  42. assert mf(1, 2) == f(1, 2)
  43. assert mf(1, y=2) == f(1, y=2)
  44. assert mf(1, y=3) == f(1, y=3)
  45. def test_memoize_curried():
  46. @curry
  47. def f(x, y=0):
  48. return x + y
  49. f2 = f(y=1)
  50. fm2 = memoize(f2)
  51. assert fm2(3) == f2(3)
  52. assert fm2(3) == f2(3)
  53. def test_memoize_partial():
  54. def f(x, y=0):
  55. return x + y
  56. f2 = partial(f, y=1)
  57. fm2 = memoize(f2)
  58. assert fm2(3) == f2(3)
  59. assert fm2(3) == f2(3)
  60. def test_memoize_key_signature():
  61. # Single argument should not be tupled as a key. No keywords.
  62. mf = memoize(lambda x: False, cache={1: True})
  63. assert mf(1) is True
  64. assert mf(2) is False
  65. # Single argument must be tupled if signature has varargs. No keywords.
  66. mf = memoize(lambda x, *args: False, cache={(1,): True, (1, 2): 2})
  67. assert mf(1) is True
  68. assert mf(2) is False
  69. assert mf(1, 1) is False
  70. assert mf(1, 2) == 2
  71. assert mf((1, 2)) is False
  72. # More than one argument is always tupled. No keywords.
  73. mf = memoize(lambda x, y: False, cache={(1, 2): True})
  74. assert mf(1, 2) is True
  75. assert mf(1, 3) is False
  76. assert raises(TypeError, lambda: mf((1, 2)))
  77. # Nullary function (no inputs) uses empty tuple as the key
  78. mf = memoize(lambda: False, cache={(): True})
  79. assert mf() is True
  80. # Single argument must be tupled if there are keyword arguments, because
  81. # keyword arguments may be passed as unnamed args.
  82. mf = memoize(lambda x, y=0: False,
  83. cache={((1,), frozenset((('y', 2),))): 2,
  84. ((1, 2), None): 3})
  85. assert mf(1, y=2) == 2
  86. assert mf(1, 2) == 3
  87. assert mf(2, y=2) is False
  88. assert mf(2, 2) is False
  89. assert mf(1) is False
  90. assert mf((1, 2)) is False
  91. # Keyword-only signatures must still have an "args" tuple.
  92. mf = memoize(lambda x=0: False, cache={(None, frozenset((('x', 1),))): 1,
  93. ((1,), None): 2})
  94. assert mf() is False
  95. assert mf(x=1) == 1
  96. assert mf(1) == 2
  97. def test_memoize_curry_cache():
  98. @memoize(cache={1: True})
  99. def f(x):
  100. return False
  101. assert f(1) is True
  102. assert f(2) is False
  103. def test_memoize_key():
  104. @memoize(key=lambda args, kwargs: args[0])
  105. def f(x, y, *args, **kwargs):
  106. return x + y
  107. assert f(1, 2) == 3
  108. assert f(1, 3) == 3
  109. def test_memoize_wrapped():
  110. def foo():
  111. """
  112. Docstring
  113. """
  114. pass
  115. memoized_foo = memoize(foo)
  116. assert memoized_foo.__wrapped__ is foo
  117. def test_curry_simple():
  118. cmul = curry(mul)
  119. double = cmul(2)
  120. assert callable(double)
  121. assert double(10) == 20
  122. assert repr(cmul) == repr(mul)
  123. cmap = curry(map)
  124. assert list(cmap(inc)([1, 2, 3])) == [2, 3, 4]
  125. assert raises(TypeError, lambda: curry())
  126. assert raises(TypeError, lambda: curry({1: 2}))
  127. def test_curry_kwargs():
  128. def f(a, b, c=10):
  129. return (a + b) * c
  130. f = curry(f)
  131. assert f(1, 2, 3) == 9
  132. assert f(1)(2, 3) == 9
  133. assert f(1, 2) == 30
  134. assert f(1, c=3)(2) == 9
  135. assert f(c=3)(1, 2) == 9
  136. def g(a=1, b=10, c=0):
  137. return a + b + c
  138. cg = curry(g, b=2)
  139. assert cg() == 3
  140. assert cg(b=3) == 4
  141. assert cg(a=0) == 2
  142. assert cg(a=0, b=1) == 1
  143. assert cg(0) == 2 # pass "a" as arg, not kwarg
  144. assert raises(TypeError, lambda: cg(1, 2)) # pass "b" as arg AND kwarg
  145. def h(x, func=int):
  146. return func(x)
  147. if platform.python_implementation() != 'PyPy'\
  148. or platform.python_version_tuple()[0] != '3': # Bug on PyPy3<2.5
  149. # __init__ must not pick func as positional arg
  150. assert curry(h)(0.0) == 0
  151. assert curry(h)(func=str)(0.0) == '0.0'
  152. assert curry(h, func=str)(0.0) == '0.0'
  153. def test_curry_passes_errors():
  154. @curry
  155. def f(a, b):
  156. if not isinstance(a, int):
  157. raise TypeError()
  158. return a + b
  159. assert f(1, 2) == 3
  160. assert raises(TypeError, lambda: f('1', 2))
  161. assert raises(TypeError, lambda: f('1')(2))
  162. assert raises(TypeError, lambda: f(1, 2, 3))
  163. def test_curry_docstring():
  164. def f(x, y):
  165. """ A docstring """
  166. return x
  167. g = curry(f)
  168. assert g.__doc__ == f.__doc__
  169. assert str(g) == str(f)
  170. assert f(1, 2) == g(1, 2)
  171. def test_curry_is_like_partial():
  172. def foo(a, b, c=1):
  173. return a + b + c
  174. p, c = partial(foo, 1, c=2), curry(foo)(1, c=2)
  175. assert p.keywords == c.keywords
  176. assert p.args == c.args
  177. assert p(3) == c(3)
  178. p, c = partial(foo, 1), curry(foo)(1)
  179. assert p.keywords == c.keywords
  180. assert p.args == c.args
  181. assert p(3) == c(3)
  182. assert p(3, c=2) == c(3, c=2)
  183. p, c = partial(foo, c=1), curry(foo)(c=1)
  184. assert p.keywords == c.keywords
  185. assert p.args == c.args
  186. assert p(1, 2) == c(1, 2)
  187. def test_curry_is_idempotent():
  188. def foo(a, b, c=1):
  189. return a + b + c
  190. f = curry(foo, 1, c=2)
  191. g = curry(f)
  192. assert isinstance(f, curry)
  193. assert isinstance(g, curry)
  194. assert not isinstance(g.func, curry)
  195. assert not hasattr(g.func, 'func')
  196. assert f.func == g.func
  197. assert f.args == g.args
  198. assert f.keywords == g.keywords
  199. def test_curry_attributes_readonly():
  200. def foo(a, b, c=1):
  201. return a + b + c
  202. f = curry(foo, 1, c=2)
  203. assert raises(AttributeError, lambda: setattr(f, 'args', (2,)))
  204. assert raises(AttributeError, lambda: setattr(f, 'keywords', {'c': 3}))
  205. assert raises(AttributeError, lambda: setattr(f, 'func', f))
  206. assert raises(AttributeError, lambda: delattr(f, 'args'))
  207. assert raises(AttributeError, lambda: delattr(f, 'keywords'))
  208. assert raises(AttributeError, lambda: delattr(f, 'func'))
  209. def test_curry_attributes_writable():
  210. def foo(a, b, c=1):
  211. return a + b + c
  212. foo.__qualname__ = 'this.is.foo'
  213. f = curry(foo, 1, c=2)
  214. assert f.__qualname__ == 'this.is.foo'
  215. f.__name__ = 'newname'
  216. f.__doc__ = 'newdoc'
  217. f.__module__ = 'newmodule'
  218. f.__qualname__ = 'newqualname'
  219. assert f.__name__ == 'newname'
  220. assert f.__doc__ == 'newdoc'
  221. assert f.__module__ == 'newmodule'
  222. assert f.__qualname__ == 'newqualname'
  223. if hasattr(f, 'func_name'):
  224. assert f.__name__ == f.func_name
  225. def test_curry_module():
  226. from toolz.curried.exceptions import merge
  227. assert merge.__module__ == 'toolz.curried.exceptions'
  228. def test_curry_comparable():
  229. def foo(a, b, c=1):
  230. return a + b + c
  231. f1 = curry(foo, 1, c=2)
  232. f2 = curry(foo, 1, c=2)
  233. g1 = curry(foo, 1, c=3)
  234. h1 = curry(foo, c=2)
  235. h2 = h1(c=2)
  236. h3 = h1()
  237. assert f1 == f2
  238. assert not (f1 != f2)
  239. assert f1 != g1
  240. assert not (f1 == g1)
  241. assert f1 != h1
  242. assert h1 == h2
  243. assert h1 == h3
  244. # test function comparison works
  245. def bar(a, b, c=1):
  246. return a + b + c
  247. b1 = curry(bar, 1, c=2)
  248. assert b1 != f1
  249. assert set([f1, f2, g1, h1, h2, h3, b1, b1()]) == set([f1, g1, h1, b1])
  250. # test unhashable input
  251. unhash1 = curry(foo, [])
  252. assert raises(TypeError, lambda: hash(unhash1))
  253. unhash2 = curry(foo, c=[])
  254. assert raises(TypeError, lambda: hash(unhash2))
  255. def test_curry_doesnot_transmogrify():
  256. # Early versions of `curry` transmogrified to `partial` objects if
  257. # only one positional argument remained even if keyword arguments
  258. # were present. Now, `curry` should always remain `curry`.
  259. def f(x, y=0):
  260. return x + y
  261. cf = curry(f)
  262. assert cf(y=1)(y=2)(y=3)(1) == f(1, 3)
  263. def test_curry_on_classmethods():
  264. class A(object):
  265. BASE = 10
  266. def __init__(self, base):
  267. self.BASE = base
  268. @curry
  269. def addmethod(self, x, y):
  270. return self.BASE + x + y
  271. @classmethod
  272. @curry
  273. def addclass(cls, x, y):
  274. return cls.BASE + x + y
  275. @staticmethod
  276. @curry
  277. def addstatic(x, y):
  278. return x + y
  279. a = A(100)
  280. assert a.addmethod(3, 4) == 107
  281. assert a.addmethod(3)(4) == 107
  282. assert A.addmethod(a, 3, 4) == 107
  283. assert A.addmethod(a)(3)(4) == 107
  284. assert a.addclass(3, 4) == 17
  285. assert a.addclass(3)(4) == 17
  286. assert A.addclass(3, 4) == 17
  287. assert A.addclass(3)(4) == 17
  288. assert a.addstatic(3, 4) == 7
  289. assert a.addstatic(3)(4) == 7
  290. assert A.addstatic(3, 4) == 7
  291. assert A.addstatic(3)(4) == 7
  292. # we want this to be of type curry
  293. assert isinstance(a.addmethod, curry)
  294. assert isinstance(A.addmethod, curry)
  295. def test_memoize_on_classmethods():
  296. class A(object):
  297. BASE = 10
  298. HASH = 10
  299. def __init__(self, base):
  300. self.BASE = base
  301. @memoize
  302. def addmethod(self, x, y):
  303. return self.BASE + x + y
  304. @classmethod
  305. @memoize
  306. def addclass(cls, x, y):
  307. return cls.BASE + x + y
  308. @staticmethod
  309. @memoize
  310. def addstatic(x, y):
  311. return x + y
  312. def __hash__(self):
  313. return self.HASH
  314. a = A(100)
  315. assert a.addmethod(3, 4) == 107
  316. assert A.addmethod(a, 3, 4) == 107
  317. a.BASE = 200
  318. assert a.addmethod(3, 4) == 107
  319. a.HASH = 200
  320. assert a.addmethod(3, 4) == 207
  321. assert a.addclass(3, 4) == 17
  322. assert A.addclass(3, 4) == 17
  323. A.BASE = 20
  324. assert A.addclass(3, 4) == 17
  325. A.HASH = 20 # hashing of class is handled by metaclass
  326. assert A.addclass(3, 4) == 17 # hence, != 27
  327. assert a.addstatic(3, 4) == 7
  328. assert A.addstatic(3, 4) == 7
  329. def test_curry_call():
  330. @curry
  331. def add(x, y):
  332. return x + y
  333. assert raises(TypeError, lambda: add.call(1))
  334. assert add(1)(2) == add.call(1, 2)
  335. assert add(1)(2) == add(1).call(2)
  336. def test_curry_bind():
  337. @curry
  338. def add(x=1, y=2):
  339. return x + y
  340. assert add() == add(1, 2)
  341. assert add.bind(10)(20) == add(10, 20)
  342. assert add.bind(10).bind(20)() == add(10, 20)
  343. assert add.bind(x=10)(y=20) == add(10, 20)
  344. assert add.bind(x=10).bind(y=20)() == add(10, 20)
  345. def test_curry_unknown_args():
  346. def add3(x, y, z):
  347. return x + y + z
  348. @curry
  349. def f(*args):
  350. return add3(*args)
  351. assert f()(1)(2)(3) == 6
  352. assert f(1)(2)(3) == 6
  353. assert f(1, 2)(3) == 6
  354. assert f(1, 2, 3) == 6
  355. assert f(1, 2)(3, 4) == f(1, 2, 3, 4)
  356. def test_curry_bad_types():
  357. assert raises(TypeError, lambda: curry(1))
  358. def test_curry_subclassable():
  359. class mycurry(curry):
  360. pass
  361. add = mycurry(lambda x, y: x+y)
  362. assert isinstance(add, curry)
  363. assert isinstance(add, mycurry)
  364. assert isinstance(add(1), mycurry)
  365. assert isinstance(add()(1), mycurry)
  366. assert add(1)(2) == 3
  367. # Should we make `_should_curry` public?
  368. """
  369. class curry2(curry):
  370. def _should_curry(self, args, kwargs, exc=None):
  371. return len(self.args) + len(args) < 2
  372. add = curry2(lambda x, y: x+y)
  373. assert isinstance(add(1), curry2)
  374. assert add(1)(2) == 3
  375. assert isinstance(add(1)(x=2), curry2)
  376. assert raises(TypeError, lambda: add(1)(x=2)(3))
  377. """
  378. def test_compose():
  379. assert compose()(0) == 0
  380. assert compose(inc)(0) == 1
  381. assert compose(double, inc)(0) == 2
  382. assert compose(str, iseven, inc, double)(3) == "False"
  383. assert compose(str, add)(1, 2) == '3'
  384. def f(a, b, c=10):
  385. return (a + b) * c
  386. assert compose(str, inc, f)(1, 2, c=3) == '10'
  387. # Define two functions with different names
  388. def f(a):
  389. return a
  390. def g(a):
  391. return a
  392. composed = compose(f, g)
  393. assert composed.__name__ == 'f_of_g'
  394. assert composed.__doc__ == 'lambda *args, **kwargs: f(g(*args, **kwargs))'
  395. # Create an object with no __name__.
  396. h = object()
  397. composed = compose(f, h)
  398. assert composed.__name__ == 'Compose'
  399. assert composed.__doc__ == 'A composition of functions'
  400. def test_pipe():
  401. assert pipe(1, inc) == 2
  402. assert pipe(1, inc, inc) == 3
  403. assert pipe(1, double, inc, iseven) is False
  404. def test_complement():
  405. # No args:
  406. assert complement(lambda: False)()
  407. assert not complement(lambda: True)()
  408. # Single arity:
  409. assert complement(iseven)(1)
  410. assert not complement(iseven)(2)
  411. assert complement(complement(iseven))(2)
  412. assert not complement(complement(isodd))(2)
  413. # Multiple arities:
  414. both_even = lambda a, b: iseven(a) and iseven(b)
  415. assert complement(both_even)(1, 2)
  416. assert not complement(both_even)(2, 2)
  417. # Generic truthiness:
  418. assert complement(lambda: "")()
  419. assert complement(lambda: 0)()
  420. assert complement(lambda: None)()
  421. assert complement(lambda: [])()
  422. assert not complement(lambda: "x")()
  423. assert not complement(lambda: 1)()
  424. assert not complement(lambda: [1])()
  425. def test_do():
  426. inc = lambda x: x + 1
  427. assert do(inc, 1) == 1
  428. log = []
  429. assert do(log.append, 1) == 1
  430. assert log == [1]
  431. def test_juxt_generator_input():
  432. data = list(range(10))
  433. juxtfunc = juxt(itemgetter(2*i) for i in range(5))
  434. assert juxtfunc(data) == (0, 2, 4, 6, 8)
  435. assert juxtfunc(data) == (0, 2, 4, 6, 8)
  436. def test_flip():
  437. def f(a, b):
  438. return a, b
  439. assert flip(f, 'a', 'b') == ('b', 'a')
  440. def test_excepts():
  441. # These are descriptors, make sure this works correctly.
  442. assert excepts.__name__ == 'excepts'
  443. assert (
  444. 'A wrapper around a function to catch exceptions and\n'
  445. ' dispatch to a handler.\n'
  446. ) in excepts.__doc__
  447. def idx(a):
  448. """idx docstring
  449. """
  450. return [1, 2].index(a)
  451. def handler(e):
  452. """handler docstring
  453. """
  454. assert isinstance(e, ValueError)
  455. return -1
  456. excepting = excepts(ValueError, idx, handler)
  457. assert excepting(1) == 0
  458. assert excepting(2) == 1
  459. assert excepting(3) == -1
  460. assert excepting.__name__ == 'idx_excepting_ValueError'
  461. assert 'idx docstring' in excepting.__doc__
  462. assert 'ValueError' in excepting.__doc__
  463. assert 'handler docstring' in excepting.__doc__
  464. def getzero(a):
  465. """getzero docstring
  466. """
  467. return a[0]
  468. excepting = excepts((IndexError, KeyError), getzero)
  469. assert excepting([]) is None
  470. assert excepting([1]) == 1
  471. assert excepting({}) is None
  472. assert excepting({0: 1}) == 1
  473. assert excepting.__name__ == 'getzero_excepting_IndexError_or_KeyError'
  474. assert 'getzero docstring' in excepting.__doc__
  475. assert 'return_none' in excepting.__doc__
  476. assert 'Returns None' in excepting.__doc__
  477. def raise_(a):
  478. """A function that raises an instance of the exception type given.
  479. """
  480. raise a()
  481. excepting = excepts((ValueError, KeyError), raise_)
  482. assert excepting(ValueError) is None
  483. assert excepting(KeyError) is None
  484. assert raises(TypeError, lambda: excepting(TypeError))
  485. assert raises(NotImplementedError, lambda: excepting(NotImplementedError))
  486. excepting = excepts(object(), object(), object())
  487. assert excepting.__name__ == 'excepting'
  488. assert excepting.__doc__ == excepts.__doc__