Commit b5b37141 authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #12428: Add a pure Python implementation of functools.partial().

Patch by Brian Thorne.
parent 65a35dca
...@@ -11,7 +11,10 @@ ...@@ -11,7 +11,10 @@
__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', __all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial'] 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial']
from _functools import partial, reduce try:
from _functools import reduce
except ImportError:
pass
from collections import namedtuple from collections import namedtuple
try: try:
from _thread import allocate_lock as Lock from _thread import allocate_lock as Lock
...@@ -136,6 +139,29 @@ except ImportError: ...@@ -136,6 +139,29 @@ except ImportError:
pass pass
################################################################################
### partial() argument application
################################################################################
def partial(func, *args, **keywords):
"""new function with partial application of the given arguments
and keywords.
"""
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
try:
from _functools import partial
except ImportError:
pass
################################################################################ ################################################################################
### LRU Cache function decorator ### LRU Cache function decorator
################################################################################ ################################################################################
......
import functools
import collections import collections
import sys import sys
import unittest import unittest
...@@ -7,17 +6,31 @@ from weakref import proxy ...@@ -7,17 +6,31 @@ from weakref import proxy
import pickle import pickle
from random import choice from random import choice
@staticmethod import functools
def PythonPartial(func, *args, **keywords):
'Pure Python approximation of partial()' original_functools = functools
def newfunc(*fargs, **fkeywords): py_functools = support.import_fresh_module('functools', blocked=['_functools'])
newkeywords = keywords.copy() c_functools = support.import_fresh_module('functools', fresh=['_functools'])
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords) class BaseTest(unittest.TestCase):
newfunc.func = func
newfunc.args = args """Base class required for testing C and Py implementations."""
newfunc.keywords = keywords
return newfunc def setUp(self):
# The module must be explicitly set so that the proper
# interaction between the c module and the python module
# can be controlled.
self.partial = self.module.partial
super(BaseTest, self).setUp()
class BaseTestC(BaseTest):
module = c_functools
class BaseTestPy(BaseTest):
module = py_functools
PythonPartial = py_functools.partial
def capture(*args, **kw): def capture(*args, **kw):
"""capture all positional and keyword arguments""" """capture all positional and keyword arguments"""
...@@ -27,31 +40,32 @@ def signature(part): ...@@ -27,31 +40,32 @@ def signature(part):
""" return the signature of a partial object """ """ return the signature of a partial object """
return (part.func, part.args, part.keywords, part.__dict__) return (part.func, part.args, part.keywords, part.__dict__)
class TestPartial(unittest.TestCase): class TestPartial(object):
thetype = functools.partial partial = functools.partial
def test_basic_examples(self): def test_basic_examples(self):
p = self.thetype(capture, 1, 2, a=10, b=20) p = self.partial(capture, 1, 2, a=10, b=20)
self.assertTrue(callable(p))
self.assertEqual(p(3, 4, b=30, c=40), self.assertEqual(p(3, 4, b=30, c=40),
((1, 2, 3, 4), dict(a=10, b=30, c=40))) ((1, 2, 3, 4), dict(a=10, b=30, c=40)))
p = self.thetype(map, lambda x: x*10) p = self.partial(map, lambda x: x*10)
self.assertEqual(list(p([1,2,3,4])), [10, 20, 30, 40]) self.assertEqual(list(p([1,2,3,4])), [10, 20, 30, 40])
def test_attributes(self): def test_attributes(self):
p = self.thetype(capture, 1, 2, a=10, b=20) p = self.partial(capture, 1, 2, a=10, b=20)
# attributes should be readable # attributes should be readable
self.assertEqual(p.func, capture) self.assertEqual(p.func, capture)
self.assertEqual(p.args, (1, 2)) self.assertEqual(p.args, (1, 2))
self.assertEqual(p.keywords, dict(a=10, b=20)) self.assertEqual(p.keywords, dict(a=10, b=20))
# attributes should not be writable # attributes should not be writable
if not isinstance(self.thetype, type): if not isinstance(self.partial, type):
return return
self.assertRaises(AttributeError, setattr, p, 'func', map) self.assertRaises(AttributeError, setattr, p, 'func', map)
self.assertRaises(AttributeError, setattr, p, 'args', (1, 2)) self.assertRaises(AttributeError, setattr, p, 'args', (1, 2))
self.assertRaises(AttributeError, setattr, p, 'keywords', dict(a=1, b=2)) self.assertRaises(AttributeError, setattr, p, 'keywords', dict(a=1, b=2))
p = self.thetype(hex) p = self.partial(hex)
try: try:
del p.__dict__ del p.__dict__
except TypeError: except TypeError:
...@@ -60,9 +74,9 @@ class TestPartial(unittest.TestCase): ...@@ -60,9 +74,9 @@ class TestPartial(unittest.TestCase):
self.fail('partial object allowed __dict__ to be deleted') self.fail('partial object allowed __dict__ to be deleted')
def test_argument_checking(self): def test_argument_checking(self):
self.assertRaises(TypeError, self.thetype) # need at least a func arg self.assertRaises(TypeError, self.partial) # need at least a func arg
try: try:
self.thetype(2)() self.partial(2)()
except TypeError: except TypeError:
pass pass
else: else:
...@@ -73,7 +87,7 @@ class TestPartial(unittest.TestCase): ...@@ -73,7 +87,7 @@ class TestPartial(unittest.TestCase):
def func(a=10, b=20): def func(a=10, b=20):
return a return a
d = {'a':3} d = {'a':3}
p = self.thetype(func, a=5) p = self.partial(func, a=5)
self.assertEqual(p(**d), 3) self.assertEqual(p(**d), 3)
self.assertEqual(d, {'a':3}) self.assertEqual(d, {'a':3})
p(b=7) p(b=7)
...@@ -82,20 +96,20 @@ class TestPartial(unittest.TestCase): ...@@ -82,20 +96,20 @@ class TestPartial(unittest.TestCase):
def test_arg_combinations(self): def test_arg_combinations(self):
# exercise special code paths for zero args in either partial # exercise special code paths for zero args in either partial
# object or the caller # object or the caller
p = self.thetype(capture) p = self.partial(capture)
self.assertEqual(p(), ((), {})) self.assertEqual(p(), ((), {}))
self.assertEqual(p(1,2), ((1,2), {})) self.assertEqual(p(1,2), ((1,2), {}))
p = self.thetype(capture, 1, 2) p = self.partial(capture, 1, 2)
self.assertEqual(p(), ((1,2), {})) self.assertEqual(p(), ((1,2), {}))
self.assertEqual(p(3,4), ((1,2,3,4), {})) self.assertEqual(p(3,4), ((1,2,3,4), {}))
def test_kw_combinations(self): def test_kw_combinations(self):
# exercise special code paths for no keyword args in # exercise special code paths for no keyword args in
# either the partial object or the caller # either the partial object or the caller
p = self.thetype(capture) p = self.partial(capture)
self.assertEqual(p(), ((), {})) self.assertEqual(p(), ((), {}))
self.assertEqual(p(a=1), ((), {'a':1})) self.assertEqual(p(a=1), ((), {'a':1}))
p = self.thetype(capture, a=1) p = self.partial(capture, a=1)
self.assertEqual(p(), ((), {'a':1})) self.assertEqual(p(), ((), {'a':1}))
self.assertEqual(p(b=2), ((), {'a':1, 'b':2})) self.assertEqual(p(b=2), ((), {'a':1, 'b':2}))
# keyword args in the call override those in the partial object # keyword args in the call override those in the partial object
...@@ -104,7 +118,7 @@ class TestPartial(unittest.TestCase): ...@@ -104,7 +118,7 @@ class TestPartial(unittest.TestCase):
def test_positional(self): def test_positional(self):
# make sure positional arguments are captured correctly # make sure positional arguments are captured correctly
for args in [(), (0,), (0,1), (0,1,2), (0,1,2,3)]: for args in [(), (0,), (0,1), (0,1,2), (0,1,2,3)]:
p = self.thetype(capture, *args) p = self.partial(capture, *args)
expected = args + ('x',) expected = args + ('x',)
got, empty = p('x') got, empty = p('x')
self.assertTrue(expected == got and empty == {}) self.assertTrue(expected == got and empty == {})
...@@ -112,14 +126,14 @@ class TestPartial(unittest.TestCase): ...@@ -112,14 +126,14 @@ class TestPartial(unittest.TestCase):
def test_keyword(self): def test_keyword(self):
# make sure keyword arguments are captured correctly # make sure keyword arguments are captured correctly
for a in ['a', 0, None, 3.5]: for a in ['a', 0, None, 3.5]:
p = self.thetype(capture, a=a) p = self.partial(capture, a=a)
expected = {'a':a,'x':None} expected = {'a':a,'x':None}
empty, got = p(x=None) empty, got = p(x=None)
self.assertTrue(expected == got and empty == ()) self.assertTrue(expected == got and empty == ())
def test_no_side_effects(self): def test_no_side_effects(self):
# make sure there are no side effects that affect subsequent calls # make sure there are no side effects that affect subsequent calls
p = self.thetype(capture, 0, a=1) p = self.partial(capture, 0, a=1)
args1, kw1 = p(1, b=2) args1, kw1 = p(1, b=2)
self.assertTrue(args1 == (0,1) and kw1 == {'a':1,'b':2}) self.assertTrue(args1 == (0,1) and kw1 == {'a':1,'b':2})
args2, kw2 = p() args2, kw2 = p()
...@@ -128,13 +142,13 @@ class TestPartial(unittest.TestCase): ...@@ -128,13 +142,13 @@ class TestPartial(unittest.TestCase):
def test_error_propagation(self): def test_error_propagation(self):
def f(x, y): def f(x, y):
x / y x / y
self.assertRaises(ZeroDivisionError, self.thetype(f, 1, 0)) self.assertRaises(ZeroDivisionError, self.partial(f, 1, 0))
self.assertRaises(ZeroDivisionError, self.thetype(f, 1), 0) self.assertRaises(ZeroDivisionError, self.partial(f, 1), 0)
self.assertRaises(ZeroDivisionError, self.thetype(f), 1, 0) self.assertRaises(ZeroDivisionError, self.partial(f), 1, 0)
self.assertRaises(ZeroDivisionError, self.thetype(f, y=0), 1) self.assertRaises(ZeroDivisionError, self.partial(f, y=0), 1)
def test_weakref(self): def test_weakref(self):
f = self.thetype(int, base=16) f = self.partial(int, base=16)
p = proxy(f) p = proxy(f)
self.assertEqual(f.func, p.func) self.assertEqual(f.func, p.func)
f = None f = None
...@@ -142,9 +156,9 @@ class TestPartial(unittest.TestCase): ...@@ -142,9 +156,9 @@ class TestPartial(unittest.TestCase):
def test_with_bound_and_unbound_methods(self): def test_with_bound_and_unbound_methods(self):
data = list(map(str, range(10))) data = list(map(str, range(10)))
join = self.thetype(str.join, '') join = self.partial(str.join, '')
self.assertEqual(join(data), '0123456789') self.assertEqual(join(data), '0123456789')
join = self.thetype(''.join) join = self.partial(''.join)
self.assertEqual(join(data), '0123456789') self.assertEqual(join(data), '0123456789')
def test_repr(self): def test_repr(self):
...@@ -152,49 +166,57 @@ class TestPartial(unittest.TestCase): ...@@ -152,49 +166,57 @@ class TestPartial(unittest.TestCase):
args_repr = ', '.join(repr(a) for a in args) args_repr = ', '.join(repr(a) for a in args)
kwargs = {'a': object(), 'b': object()} kwargs = {'a': object(), 'b': object()}
kwargs_repr = ', '.join("%s=%r" % (k, v) for k, v in kwargs.items()) kwargs_repr = ', '.join("%s=%r" % (k, v) for k, v in kwargs.items())
if self.thetype is functools.partial: if self.partial is functools.partial:
name = 'functools.partial' name = 'functools.partial'
else: else:
name = self.thetype.__name__ name = self.partial.__name__
f = self.thetype(capture) f = self.partial(capture)
self.assertEqual('{}({!r})'.format(name, capture), self.assertEqual('{}({!r})'.format(name, capture),
repr(f)) repr(f))
f = self.thetype(capture, *args) f = self.partial(capture, *args)
self.assertEqual('{}({!r}, {})'.format(name, capture, args_repr), self.assertEqual('{}({!r}, {})'.format(name, capture, args_repr),
repr(f)) repr(f))
f = self.thetype(capture, **kwargs) f = self.partial(capture, **kwargs)
self.assertEqual('{}({!r}, {})'.format(name, capture, kwargs_repr), self.assertEqual('{}({!r}, {})'.format(name, capture, kwargs_repr),
repr(f)) repr(f))
f = self.thetype(capture, *args, **kwargs) f = self.partial(capture, *args, **kwargs)
self.assertEqual('{}({!r}, {}, {})'.format(name, capture, args_repr, kwargs_repr), self.assertEqual('{}({!r}, {}, {})'.format(name, capture, args_repr, kwargs_repr),
repr(f)) repr(f))
def test_pickle(self): def test_pickle(self):
f = self.thetype(signature, 'asdf', bar=True) f = self.partial(signature, 'asdf', bar=True)
f.add_something_to__dict__ = True f.add_something_to__dict__ = True
f_copy = pickle.loads(pickle.dumps(f)) f_copy = pickle.loads(pickle.dumps(f))
self.assertEqual(signature(f), signature(f_copy)) self.assertEqual(signature(f), signature(f_copy))
class PartialSubclass(functools.partial): class TestPartialC(BaseTestC, TestPartial):
pass pass
class TestPartialSubclass(TestPartial): class TestPartialPy(BaseTestPy, TestPartial):
thetype = PartialSubclass def test_pickle(self):
raise unittest.SkipTest("Python implementation of partial isn't picklable")
class TestPythonPartial(TestPartial): def test_repr(self):
raise unittest.SkipTest("Python implementation of partial uses own repr")
class TestPartialCSubclass(BaseTestC, TestPartial):
class PartialSubclass(c_functools.partial):
pass
partial = staticmethod(PartialSubclass)
thetype = PythonPartial class TestPartialPySubclass(TestPartialPy):
# the python version hasn't a nice repr class PartialSubclass(c_functools.partial):
def test_repr(self): pass pass
# the python version isn't picklable partial = staticmethod(PartialSubclass)
def test_pickle(self): pass
class TestUpdateWrapper(unittest.TestCase): class TestUpdateWrapper(unittest.TestCase):
...@@ -320,7 +342,7 @@ class TestWraps(TestUpdateWrapper): ...@@ -320,7 +342,7 @@ class TestWraps(TestUpdateWrapper):
self.assertEqual(wrapper.__qualname__, f.__qualname__) self.assertEqual(wrapper.__qualname__, f.__qualname__)
self.assertEqual(wrapper.attr, 'This is also a test') self.assertEqual(wrapper.attr, 'This is also a test')
@unittest.skipIf(not sys.flags.optimize <= 1, @unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above") "Docstrings are omitted with -O2 and above")
def test_default_update_doc(self): def test_default_update_doc(self):
wrapper, _ = self._default_update() wrapper, _ = self._default_update()
...@@ -441,24 +463,28 @@ class TestReduce(unittest.TestCase): ...@@ -441,24 +463,28 @@ class TestReduce(unittest.TestCase):
d = {"one": 1, "two": 2, "three": 3} d = {"one": 1, "two": 2, "three": 3}
self.assertEqual(self.func(add, d), "".join(d.keys())) self.assertEqual(self.func(add, d), "".join(d.keys()))
class TestCmpToKey(unittest.TestCase): class TestCmpToKey(object):
def test_cmp_to_key(self): def test_cmp_to_key(self):
def cmp1(x, y): def cmp1(x, y):
return (x > y) - (x < y) return (x > y) - (x < y)
key = functools.cmp_to_key(cmp1) key = self.cmp_to_key(cmp1)
self.assertEqual(key(3), key(3)) self.assertEqual(key(3), key(3))
self.assertGreater(key(3), key(1)) self.assertGreater(key(3), key(1))
self.assertGreaterEqual(key(3), key(3))
def cmp2(x, y): def cmp2(x, y):
return int(x) - int(y) return int(x) - int(y)
key = functools.cmp_to_key(cmp2) key = self.cmp_to_key(cmp2)
self.assertEqual(key(4.0), key('4')) self.assertEqual(key(4.0), key('4'))
self.assertLess(key(2), key('35')) self.assertLess(key(2), key('35'))
self.assertLessEqual(key(2), key('35'))
self.assertNotEqual(key(2), key('35'))
def test_cmp_to_key_arguments(self): def test_cmp_to_key_arguments(self):
def cmp1(x, y): def cmp1(x, y):
return (x > y) - (x < y) return (x > y) - (x < y)
key = functools.cmp_to_key(mycmp=cmp1) key = self.cmp_to_key(mycmp=cmp1)
self.assertEqual(key(obj=3), key(obj=3)) self.assertEqual(key(obj=3), key(obj=3))
self.assertGreater(key(obj=3), key(obj=1)) self.assertGreater(key(obj=3), key(obj=1))
with self.assertRaises((TypeError, AttributeError)): with self.assertRaises((TypeError, AttributeError)):
...@@ -466,10 +492,10 @@ class TestCmpToKey(unittest.TestCase): ...@@ -466,10 +492,10 @@ class TestCmpToKey(unittest.TestCase):
with self.assertRaises((TypeError, AttributeError)): with self.assertRaises((TypeError, AttributeError)):
1 < key(3) # lhs is not a K object 1 < key(3) # lhs is not a K object
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
key = functools.cmp_to_key() # too few args key = self.cmp_to_key() # too few args
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
key = functools.cmp_to_key(cmp1, None) # too many args key = self.module.cmp_to_key(cmp1, None) # too many args
key = functools.cmp_to_key(cmp1) key = self.cmp_to_key(cmp1)
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
key() # too few args key() # too few args
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
...@@ -478,7 +504,7 @@ class TestCmpToKey(unittest.TestCase): ...@@ -478,7 +504,7 @@ class TestCmpToKey(unittest.TestCase):
def test_bad_cmp(self): def test_bad_cmp(self):
def cmp1(x, y): def cmp1(x, y):
raise ZeroDivisionError raise ZeroDivisionError
key = functools.cmp_to_key(cmp1) key = self.cmp_to_key(cmp1)
with self.assertRaises(ZeroDivisionError): with self.assertRaises(ZeroDivisionError):
key(3) > key(1) key(3) > key(1)
...@@ -493,13 +519,13 @@ class TestCmpToKey(unittest.TestCase): ...@@ -493,13 +519,13 @@ class TestCmpToKey(unittest.TestCase):
def test_obj_field(self): def test_obj_field(self):
def cmp1(x, y): def cmp1(x, y):
return (x > y) - (x < y) return (x > y) - (x < y)
key = functools.cmp_to_key(mycmp=cmp1) key = self.cmp_to_key(mycmp=cmp1)
self.assertEqual(key(50).obj, 50) self.assertEqual(key(50).obj, 50)
def test_sort_int(self): def test_sort_int(self):
def mycmp(x, y): def mycmp(x, y):
return y - x return y - x
self.assertEqual(sorted(range(5), key=functools.cmp_to_key(mycmp)), self.assertEqual(sorted(range(5), key=self.cmp_to_key(mycmp)),
[4, 3, 2, 1, 0]) [4, 3, 2, 1, 0])
def test_sort_int_str(self): def test_sort_int_str(self):
...@@ -507,18 +533,24 @@ class TestCmpToKey(unittest.TestCase): ...@@ -507,18 +533,24 @@ class TestCmpToKey(unittest.TestCase):
x, y = int(x), int(y) x, y = int(x), int(y)
return (x > y) - (x < y) return (x > y) - (x < y)
values = [5, '3', 7, 2, '0', '1', 4, '10', 1] values = [5, '3', 7, 2, '0', '1', 4, '10', 1]
values = sorted(values, key=functools.cmp_to_key(mycmp)) values = sorted(values, key=self.cmp_to_key(mycmp))
self.assertEqual([int(value) for value in values], self.assertEqual([int(value) for value in values],
[0, 1, 1, 2, 3, 4, 5, 7, 10]) [0, 1, 1, 2, 3, 4, 5, 7, 10])
def test_hash(self): def test_hash(self):
def mycmp(x, y): def mycmp(x, y):
return y - x return y - x
key = functools.cmp_to_key(mycmp) key = self.cmp_to_key(mycmp)
k = key(10) k = key(10)
self.assertRaises(TypeError, hash, k) self.assertRaises(TypeError, hash, k)
self.assertNotIsInstance(k, collections.Hashable) self.assertNotIsInstance(k, collections.Hashable)
class TestCmpToKeyC(BaseTestC, TestCmpToKey):
cmp_to_key = c_functools.cmp_to_key
class TestCmpToKeyPy(BaseTestPy, TestCmpToKey):
cmp_to_key = staticmethod(py_functools.cmp_to_key)
class TestTotalOrdering(unittest.TestCase): class TestTotalOrdering(unittest.TestCase):
def test_total_ordering_lt(self): def test_total_ordering_lt(self):
...@@ -623,7 +655,7 @@ class TestLRU(unittest.TestCase): ...@@ -623,7 +655,7 @@ class TestLRU(unittest.TestCase):
def test_lru(self): def test_lru(self):
def orig(x, y): def orig(x, y):
return 3*x+y return 3 * x + y
f = functools.lru_cache(maxsize=20)(orig) f = functools.lru_cache(maxsize=20)(orig)
hits, misses, maxsize, currsize = f.cache_info() hits, misses, maxsize, currsize = f.cache_info()
self.assertEqual(maxsize, 20) self.assertEqual(maxsize, 20)
...@@ -728,7 +760,7 @@ class TestLRU(unittest.TestCase): ...@@ -728,7 +760,7 @@ class TestLRU(unittest.TestCase):
# Verify that user_function exceptions get passed through without # Verify that user_function exceptions get passed through without
# creating a hard-to-read chained exception. # creating a hard-to-read chained exception.
# http://bugs.python.org/issue13177 # http://bugs.python.org/issue13177
for maxsize in (None, 100): for maxsize in (None, 128):
@functools.lru_cache(maxsize) @functools.lru_cache(maxsize)
def func(i): def func(i):
return 'abc'[i] return 'abc'[i]
...@@ -741,7 +773,7 @@ class TestLRU(unittest.TestCase): ...@@ -741,7 +773,7 @@ class TestLRU(unittest.TestCase):
func(15) func(15)
def test_lru_with_types(self): def test_lru_with_types(self):
for maxsize in (None, 100): for maxsize in (None, 128):
@functools.lru_cache(maxsize=maxsize, typed=True) @functools.lru_cache(maxsize=maxsize, typed=True)
def square(x): def square(x):
return x * x return x * x
...@@ -756,14 +788,46 @@ class TestLRU(unittest.TestCase): ...@@ -756,14 +788,46 @@ class TestLRU(unittest.TestCase):
self.assertEqual(square.cache_info().hits, 4) self.assertEqual(square.cache_info().hits, 4)
self.assertEqual(square.cache_info().misses, 4) self.assertEqual(square.cache_info().misses, 4)
def test_lru_with_keyword_args(self):
@functools.lru_cache()
def fib(n):
if n < 2:
return n
return fib(n=n-1) + fib(n=n-2)
self.assertEqual(
[fib(n=number) for number in range(16)],
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
)
self.assertEqual(fib.cache_info(),
functools._CacheInfo(hits=28, misses=16, maxsize=128, currsize=16))
fib.cache_clear()
self.assertEqual(fib.cache_info(),
functools._CacheInfo(hits=0, misses=0, maxsize=128, currsize=0))
def test_lru_with_keyword_args_maxsize_none(self):
@functools.lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n=n-1) + fib(n=n-2)
self.assertEqual([fib(n=number) for number in range(16)],
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610])
self.assertEqual(fib.cache_info(),
functools._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16))
fib.cache_clear()
self.assertEqual(fib.cache_info(),
functools._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))
def test_main(verbose=None): def test_main(verbose=None):
test_classes = ( test_classes = (
TestPartial, TestPartialC,
TestPartialSubclass, TestPartialPy,
TestPythonPartial, TestPartialCSubclass,
TestPartialPySubclass,
TestUpdateWrapper, TestUpdateWrapper,
TestTotalOrdering, TestTotalOrdering,
TestCmpToKey, TestCmpToKeyC,
TestCmpToKeyPy,
TestWraps, TestWraps,
TestReduce, TestReduce,
TestLRU, TestLRU,
......
...@@ -1166,6 +1166,7 @@ Tobias Thelen ...@@ -1166,6 +1166,7 @@ Tobias Thelen
Nicolas M. Thiéry Nicolas M. Thiéry
James Thomas James Thomas
Robin Thomas Robin Thomas
Brian Thorne
Stephen Thorne Stephen Thorne
Jeremy Thurgood Jeremy Thurgood
Eric Tiedemann Eric Tiedemann
......
...@@ -124,6 +124,9 @@ Core and Builtins ...@@ -124,6 +124,9 @@ Core and Builtins
Library Library
------- -------
- Issue #12428: Add a pure Python implementation of functools.partial().
Patch by Brian Thorne.
- Issue #16140: The subprocess module no longer double closes its child - Issue #16140: The subprocess module no longer double closes its child
subprocess.PIPE parent file descriptors on child error prior to exec(). subprocess.PIPE parent file descriptors on child error prior to exec().
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment