cpython: fcfaca024160 (original) (raw)
--- a/Lib/functools.py +++ b/Lib/functools.py @@ -11,7 +11,10 @@ all = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial'] -from _functools import partial, reduce +try:
from collections import namedtuple try: from _thread import allocate_lock as Lock @@ -137,6 +140,29 @@ except ImportError: ################################################################################ +### 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()[](#l1.27)
newkeywords.update(fkeywords)[](#l1.28)
return func(*(args + fargs), **newkeywords)[](#l1.29)
- newfunc.func = func
- newfunc.args = args
- newfunc.keywords = keywords
- return newfunc
+ + +################################################################################
LRU Cache function decorator
################################################################################
--- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -1,4 +1,3 @@ -import functools import collections import sys import unittest @@ -7,17 +6,31 @@ from weakref import proxy import pickle from random import choice -@staticmethod -def PythonPartial(func, *args, **keywords):
- 'Pure Python approximation of partial()'
- def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()[](#l2.16)
newkeywords.update(fkeywords)[](#l2.17)
return func(*(args + fargs), **newkeywords)[](#l2.18)
- newfunc.func = func
- newfunc.args = args
- newfunc.keywords = keywords
- return newfunc
+import functools + +original_functools = functools +py_functools = support.import_fresh_module('functools', blocked=['_functools']) +c_functools = support.import_fresh_module('functools', fresh=['_functools']) + +class BaseTest(unittest.TestCase): +
# The module must be explicitly set so that the proper[](#l2.35)
# interaction between the c module and the python module[](#l2.36)
# can be controlled.[](#l2.37)
self.partial = self.module.partial[](#l2.38)
super(BaseTest, self).setUp()[](#l2.39)
+ +class BaseTestPy(BaseTest):
+ +PythonPartial = py_functools.partial def capture(*args, **kw): """capture all positional and keyword arguments""" @@ -27,31 +40,32 @@ def signature(part): """ return the signature of a partial object """ return (part.func, part.args, part.keywords, part.dict) -class TestPartial(unittest.TestCase): +class TestPartial(object):
def test_basic_examples(self):
p = self.thetype(capture, 1, 2, a=10, b=20)[](#l2.62)
p = self.partial(capture, 1, 2, a=10, b=20)[](#l2.63)
self.assertTrue(callable(p))[](#l2.64) self.assertEqual(p(3, 4, b=30, c=40),[](#l2.65) ((1, 2, 3, 4), dict(a=10, b=30, c=40)))[](#l2.66)
p = self.thetype(map, lambda x: x*10)[](#l2.67)
p = self.partial(map, lambda x: x*10)[](#l2.68) self.assertEqual(list(p([1,2,3,4])), [10, 20, 30, 40])[](#l2.69)
p = self.thetype(capture, 1, 2, a=10, b=20)[](#l2.72)
p = self.partial(capture, 1, 2, a=10, b=20)[](#l2.73) # attributes should be readable[](#l2.74) self.assertEqual(p.func, capture)[](#l2.75) self.assertEqual(p.args, (1, 2))[](#l2.76) self.assertEqual(p.keywords, dict(a=10, b=20))[](#l2.77) # attributes should not be writable[](#l2.78)
if not isinstance(self.thetype, type):[](#l2.79)
if not isinstance(self.partial, type):[](#l2.80) return[](#l2.81) self.assertRaises(AttributeError, setattr, p, 'func', map)[](#l2.82) self.assertRaises(AttributeError, setattr, p, 'args', (1, 2))[](#l2.83) self.assertRaises(AttributeError, setattr, p, 'keywords', dict(a=1, b=2))[](#l2.84)
p = self.thetype(hex)[](#l2.86)
p = self.partial(hex)[](#l2.87) try:[](#l2.88) del p.__dict__[](#l2.89) except TypeError:[](#l2.90)
@@ -60,9 +74,9 @@ class TestPartial(unittest.TestCase): self.fail('partial object allowed dict to be deleted') def test_argument_checking(self):
self.assertRaises(TypeError, self.thetype) # need at least a func arg[](#l2.95)
self.assertRaises(TypeError, self.partial) # need at least a func arg[](#l2.96) try:[](#l2.97)
self.thetype(2)()[](#l2.98)
self.partial(2)()[](#l2.99) except TypeError:[](#l2.100) pass[](#l2.101) else:[](#l2.102)
@@ -73,7 +87,7 @@ class TestPartial(unittest.TestCase): def func(a=10, b=20): return a d = {'a':3}
p = self.thetype(func, a=5)[](#l2.107)
p = self.partial(func, a=5)[](#l2.108) self.assertEqual(p(**d), 3)[](#l2.109) self.assertEqual(d, {'a':3})[](#l2.110) p(b=7)[](#l2.111)
@@ -82,20 +96,20 @@ class TestPartial(unittest.TestCase): def test_arg_combinations(self): # exercise special code paths for zero args in either partial # object or the caller
p = self.thetype(capture)[](#l2.116)
p = self.partial(capture)[](#l2.117) self.assertEqual(p(), ((), {}))[](#l2.118) self.assertEqual(p(1,2), ((1,2), {}))[](#l2.119)
p = self.thetype(capture, 1, 2)[](#l2.120)
p = self.partial(capture, 1, 2)[](#l2.121) self.assertEqual(p(), ((1,2), {}))[](#l2.122) self.assertEqual(p(3,4), ((1,2,3,4), {}))[](#l2.123)
def test_kw_combinations(self): # exercise special code paths for no keyword args in # either the partial object or the caller
p = self.thetype(capture)[](#l2.128)
p = self.partial(capture)[](#l2.129) self.assertEqual(p(), ((), {}))[](#l2.130) self.assertEqual(p(a=1), ((), {'a':1}))[](#l2.131)
p = self.thetype(capture, a=1)[](#l2.132)
p = self.partial(capture, a=1)[](#l2.133) self.assertEqual(p(), ((), {'a':1}))[](#l2.134) self.assertEqual(p(b=2), ((), {'a':1, 'b':2}))[](#l2.135) # keyword args in the call override those in the partial object[](#l2.136)
@@ -104,7 +118,7 @@ class TestPartial(unittest.TestCase): def test_positional(self): # make sure positional arguments are captured correctly for args in [(), (0,), (0,1), (0,1,2), (0,1,2,3)]:
p = self.thetype(capture, *args)[](#l2.141)
p = self.partial(capture, *args)[](#l2.142) expected = args + ('x',)[](#l2.143) got, empty = p('x')[](#l2.144) self.assertTrue(expected == got and empty == {})[](#l2.145)
@@ -112,14 +126,14 @@ class TestPartial(unittest.TestCase): def test_keyword(self): # make sure keyword arguments are captured correctly for a in ['a', 0, None, 3.5]:
p = self.thetype(capture, a=a)[](#l2.150)
p = self.partial(capture, a=a)[](#l2.151) expected = {'a':a,'x':None}[](#l2.152) empty, got = p(x=None)[](#l2.153) self.assertTrue(expected == got and empty == ())[](#l2.154)
def test_no_side_effects(self): # make sure there are no side effects that affect subsequent calls
p = self.thetype(capture, 0, a=1)[](#l2.158)
p = self.partial(capture, 0, a=1)[](#l2.159) args1, kw1 = p(1, b=2)[](#l2.160) self.assertTrue(args1 == (0,1) and kw1 == {'a':1,'b':2})[](#l2.161) args2, kw2 = p()[](#l2.162)
@@ -128,13 +142,13 @@ class TestPartial(unittest.TestCase): def test_error_propagation(self): def f(x, y): x / y
self.assertRaises(ZeroDivisionError, self.thetype(f, 1, 0))[](#l2.167)
self.assertRaises(ZeroDivisionError, self.thetype(f, 1), 0)[](#l2.168)
self.assertRaises(ZeroDivisionError, self.thetype(f), 1, 0)[](#l2.169)
self.assertRaises(ZeroDivisionError, self.thetype(f, y=0), 1)[](#l2.170)
self.assertRaises(ZeroDivisionError, self.partial(f, 1, 0))[](#l2.171)
self.assertRaises(ZeroDivisionError, self.partial(f, 1), 0)[](#l2.172)
self.assertRaises(ZeroDivisionError, self.partial(f), 1, 0)[](#l2.173)
self.assertRaises(ZeroDivisionError, self.partial(f, y=0), 1)[](#l2.174)
f = self.thetype(int, base=16)[](#l2.177)
f = self.partial(int, base=16)[](#l2.178) p = proxy(f)[](#l2.179) self.assertEqual(f.func, p.func)[](#l2.180) f = None[](#l2.181)
@@ -142,9 +156,9 @@ class TestPartial(unittest.TestCase): def test_with_bound_and_unbound_methods(self): data = list(map(str, range(10)))
join = self.thetype(str.join, '')[](#l2.186)
join = self.partial(str.join, '')[](#l2.187) self.assertEqual(join(data), '0123456789')[](#l2.188)
join = self.thetype(''.join)[](#l2.189)
join = self.partial(''.join)[](#l2.190) self.assertEqual(join(data), '0123456789')[](#l2.191)
def test_repr(self): @@ -152,49 +166,57 @@ class TestPartial(unittest.TestCase): args_repr = ', '.join(repr(a) for a in args) kwargs = {'a': object(), 'b': object()} kwargs_repr = ', '.join("%s=%r" % (k, v) for k, v in kwargs.items())
if self.thetype is functools.partial:[](#l2.198)
if self.partial is functools.partial:[](#l2.199) name = 'functools.partial'[](#l2.200) else:[](#l2.201)
name = self.thetype.__name__[](#l2.202)
name = self.partial.__name__[](#l2.203)
f = self.thetype(capture)[](#l2.205)
f = self.partial(capture)[](#l2.206) self.assertEqual('{}({!r})'.format(name, capture),[](#l2.207) repr(f))[](#l2.208)
f = self.thetype(capture, *args)[](#l2.210)
f = self.partial(capture, *args)[](#l2.211) self.assertEqual('{}({!r}, {})'.format(name, capture, args_repr),[](#l2.212) repr(f))[](#l2.213)
f = self.thetype(capture, **kwargs)[](#l2.215)
f = self.partial(capture, **kwargs)[](#l2.216) self.assertEqual('{}({!r}, {})'.format(name, capture, kwargs_repr),[](#l2.217) repr(f))[](#l2.218)
f = self.thetype(capture, *args, **kwargs)[](#l2.220)
f = self.partial(capture, *args, **kwargs)[](#l2.221) self.assertEqual('{}({!r}, {}, {})'.format(name, capture, args_repr, kwargs_repr),[](#l2.222) repr(f))[](#l2.223)
f = self.thetype(signature, 'asdf', bar=True)[](#l2.226)
f = self.partial(signature, 'asdf', bar=True)[](#l2.227) f.add_something_to__dict__ = True[](#l2.228) f_copy = pickle.loads(pickle.dumps(f))[](#l2.229) self.assertEqual(signature(f), signature(f_copy))[](#l2.230)
-class PartialSubclass(functools.partial): +class TestPartialC(BaseTestC, TestPartial): pass -class TestPartialSubclass(TestPartial): +class TestPartialPy(BaseTestPy, TestPartial):
- def test_pickle(self):
raise unittest.SkipTest("Python implementation of partial isn't picklable")[](#l2.241)
- def test_repr(self):
raise unittest.SkipTest("Python implementation of partial uses own repr")[](#l2.244)
-class TestPythonPartial(TestPartial): +class TestPartialCSubclass(BaseTestC, TestPartial):
+class TestPartialPySubclass(TestPartialPy):
class TestUpdateWrapper(unittest.TestCase): @@ -320,7 +342,7 @@ class TestWraps(TestUpdateWrapper): self.assertEqual(wrapper.qualname, f.qualname) self.assertEqual(wrapper.attr, 'This is also a test')
- @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_default_update_doc(self): wrapper, _ = self._default_update()
@@ -441,24 +463,28 @@ class TestReduce(unittest.TestCase): d = {"one": 1, "two": 2, "three": 3} self.assertEqual(self.func(add, d), "".join(d.keys())) -class TestCmpToKey(unittest.TestCase): +class TestCmpToKey(object): def test_cmp_to_key(self): def cmp1(x, y): return (x > y) - (x < y)
key = functools.cmp_to_key(cmp1)[](#l2.287)
key = self.cmp_to_key(cmp1)[](#l2.288) self.assertEqual(key(3), key(3))[](#l2.289) self.assertGreater(key(3), key(1))[](#l2.290)
self.assertGreaterEqual(key(3), key(3))[](#l2.291)
+ def cmp2(x, y): return int(x) - int(y)
key = functools.cmp_to_key(cmp2)[](#l2.295)
key = self.cmp_to_key(cmp2)[](#l2.296) self.assertEqual(key(4.0), key('4'))[](#l2.297) self.assertLess(key(2), key('35'))[](#l2.298)
self.assertLessEqual(key(2), key('35'))[](#l2.299)
self.assertNotEqual(key(2), key('35'))[](#l2.300)
def test_cmp_to_key_arguments(self): def cmp1(x, y): return (x > y) - (x < y)
key = functools.cmp_to_key(mycmp=cmp1)[](#l2.305)
key = self.cmp_to_key(mycmp=cmp1)[](#l2.306) self.assertEqual(key(obj=3), key(obj=3))[](#l2.307) self.assertGreater(key(obj=3), key(obj=1))[](#l2.308) with self.assertRaises((TypeError, AttributeError)):[](#l2.309)
@@ -466,10 +492,10 @@ class TestCmpToKey(unittest.TestCase): with self.assertRaises((TypeError, AttributeError)): 1 < key(3) # lhs is not a K object with self.assertRaises(TypeError):
key = functools.cmp_to_key() # too few args[](#l2.314)
key = self.cmp_to_key() # too few args[](#l2.315) with self.assertRaises(TypeError):[](#l2.316)
key = functools.cmp_to_key(cmp1, None) # too many args[](#l2.317)
key = functools.cmp_to_key(cmp1)[](#l2.318)
key = self.module.cmp_to_key(cmp1, None) # too many args[](#l2.319)
key = self.cmp_to_key(cmp1)[](#l2.320) with self.assertRaises(TypeError):[](#l2.321) key() # too few args[](#l2.322) with self.assertRaises(TypeError):[](#l2.323)
@@ -478,7 +504,7 @@ class TestCmpToKey(unittest.TestCase): def test_bad_cmp(self): def cmp1(x, y): raise ZeroDivisionError
key = functools.cmp_to_key(cmp1)[](#l2.328)
key = self.cmp_to_key(cmp1)[](#l2.329) with self.assertRaises(ZeroDivisionError):[](#l2.330) key(3) > key(1)[](#l2.331)
@@ -493,13 +519,13 @@ class TestCmpToKey(unittest.TestCase): def test_obj_field(self): def cmp1(x, y): return (x > y) - (x < y)
key = functools.cmp_to_key(mycmp=cmp1)[](#l2.337)
key = self.cmp_to_key(mycmp=cmp1)[](#l2.338) self.assertEqual(key(50).obj, 50)[](#l2.339)
def test_sort_int(self): def mycmp(x, y): return y - x
self.assertEqual(sorted(range(5), key=functools.cmp_to_key(mycmp)),[](#l2.344)
self.assertEqual(sorted(range(5), key=self.cmp_to_key(mycmp)),[](#l2.345) [4, 3, 2, 1, 0])[](#l2.346)
def test_sort_int_str(self): @@ -507,18 +533,24 @@ class TestCmpToKey(unittest.TestCase): x, y = int(x), int(y) return (x > y) - (x < y) values = [5, '3', 7, 2, '0', '1', 4, '10', 1]
values = sorted(values, key=functools.cmp_to_key(mycmp))[](#l2.353)
values = sorted(values, key=self.cmp_to_key(mycmp))[](#l2.354) self.assertEqual([int(value) for value in values],[](#l2.355) [0, 1, 1, 2, 3, 4, 5, 7, 10])[](#l2.356)
def test_hash(self): def mycmp(x, y): return y - x
key = functools.cmp_to_key(mycmp)[](#l2.361)
key = self.cmp_to_key(mycmp)[](#l2.362) k = key(10)[](#l2.363) self.assertRaises(TypeError, hash, k)[](#l2.364) self.assertNotIsInstance(k, collections.Hashable)[](#l2.365)
+class TestCmpToKeyC(BaseTestC, TestCmpToKey):
+ +class TestCmpToKeyPy(BaseTestPy, TestCmpToKey):
+ class TestTotalOrdering(unittest.TestCase): def test_total_ordering_lt(self): @@ -623,7 +655,7 @@ class TestLRU(unittest.TestCase): def test_lru(self): def orig(x, y):
return 3*x+y[](#l2.380)
return 3 * x + y[](#l2.381) f = functools.lru_cache(maxsize=20)(orig)[](#l2.382) hits, misses, maxsize, currsize = f.cache_info()[](#l2.383) self.assertEqual(maxsize, 20)[](#l2.384)
@@ -728,7 +760,7 @@ class TestLRU(unittest.TestCase): # Verify that user_function exceptions get passed through without # creating a hard-to-read chained exception. # http://bugs.python.org/issue13177[](#l2.388)
for maxsize in (None, 100):[](#l2.389)
for maxsize in (None, 128):[](#l2.390) @functools.lru_cache(maxsize)[](#l2.391) def func(i):[](#l2.392) return 'abc'[i][](#l2.393)
@@ -741,7 +773,7 @@ class TestLRU(unittest.TestCase): func(15) def test_lru_with_types(self):
for maxsize in (None, 100):[](#l2.398)
for maxsize in (None, 128):[](#l2.399) @functools.lru_cache(maxsize=maxsize, typed=True)[](#l2.400) def square(x):[](#l2.401) return x * x[](#l2.402)
@@ -756,14 +788,46 @@ class TestLRU(unittest.TestCase): self.assertEqual(square.cache_info().hits, 4) self.assertEqual(square.cache_info().misses, 4)
- def test_lru_with_keyword_args(self):
@functools.lru_cache()[](#l2.408)
def fib(n):[](#l2.409)
if n < 2:[](#l2.410)
return n[](#l2.411)
return fib(n=n-1) + fib(n=n-2)[](#l2.412)
self.assertEqual([](#l2.413)
[fib(n=number) for number in range(16)],[](#l2.414)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610][](#l2.415)
)[](#l2.416)
self.assertEqual(fib.cache_info(),[](#l2.417)
functools._CacheInfo(hits=28, misses=16, maxsize=128, currsize=16))[](#l2.418)
fib.cache_clear()[](#l2.419)
self.assertEqual(fib.cache_info(),[](#l2.420)
functools._CacheInfo(hits=0, misses=0, maxsize=128, currsize=0))[](#l2.421)
- def test_lru_with_keyword_args_maxsize_none(self):
@functools.lru_cache(maxsize=None)[](#l2.424)
def fib(n):[](#l2.425)
if n < 2:[](#l2.426)
return n[](#l2.427)
return fib(n=n-1) + fib(n=n-2)[](#l2.428)
self.assertEqual([fib(n=number) for number in range(16)],[](#l2.429)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610])[](#l2.430)
self.assertEqual(fib.cache_info(),[](#l2.431)
functools._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16))[](#l2.432)
fib.cache_clear()[](#l2.433)
self.assertEqual(fib.cache_info(),[](#l2.434)
functools._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))[](#l2.435)
+ def test_main(verbose=None): test_classes = (
TestPartial,[](#l2.439)
TestPartialSubclass,[](#l2.440)
TestPythonPartial,[](#l2.441)
TestPartialC,[](#l2.442)
TestPartialPy,[](#l2.443)
TestPartialCSubclass,[](#l2.444)
TestPartialPySubclass,[](#l2.445) TestUpdateWrapper,[](#l2.446) TestTotalOrdering,[](#l2.447)
TestCmpToKey,[](#l2.448)
TestCmpToKeyC,[](#l2.449)
TestCmpToKeyPy,[](#l2.450) TestWraps,[](#l2.451) TestReduce,[](#l2.452) TestLRU,[](#l2.453)
--- a/Misc/ACKS +++ b/Misc/ACKS @@ -1166,6 +1166,7 @@ Tobias Thelen Nicolas M. ThiƩry James Thomas Robin Thomas +Brian Thorne Stephen Thorne Jeremy Thurgood Eric Tiedemann
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -124,6 +124,9 @@ Core and Builtins Library ------- +- Issue #12428: Add a pure Python implementation of functools.partial().