Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9c323f8

Browse files
committed
SF patch #941881: PEP 309 Implementation (Partial Function Application).
Combined efforts of many including Peter Harris, Hye-Shik Chang, Martin v. Löwis, Nick Coghlan, Paul Moore, and Raymond Hettinger.
1 parent 049ade2 commit 9c323f8

9 files changed

Lines changed: 489 additions & 0 deletions

File tree

Doc/lib/lib.tex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ \chapter*{Front Matter\label{front}}
132132
\input{libarray}
133133
\input{libsets}
134134
\input{libitertools}
135+
\input{libfunctional}
135136
\input{libcfgparser}
136137
\input{libfileinput}
137138
\input{libcalendar}

Doc/lib/libfunctional.tex

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
\section{\module{functional} ---
2+
Higher order functions and operations on callable objects.}
3+
4+
\declaremodule{standard}{functional} % standard library, in Python
5+
6+
\moduleauthor{Peter Harris}{[email protected]}
7+
\moduleauthor{Raymond Hettinger}{[email protected]}
8+
\sectionauthor{Peter Harris}{[email protected]}
9+
10+
\modulesynopsis{Higher-order functions and operations on callable objects.}
11+
12+
13+
The \module{functional} module is for higher-order functions: functions
14+
that act on or return other functions. In general, any callable object can
15+
be treated as a function for the purposes of this module.
16+
17+
18+
The \module{functional} module defines the following function:
19+
20+
\begin{funcdesc}{partial}{func\optional{,*args}\optional{, **keywords}}
21+
Return a new \class{partial} object which when called will behave like
22+
\var{func} called with the positional arguments \var{args} and keyword
23+
arguments \var{keywords}. If more arguments are supplied to the call, they
24+
are appended to \var{args}. If additional keyword arguments are supplied,
25+
they extend and override \var{keywords}. Roughly equivalent to:
26+
\begin{verbatim}
27+
def partial(func, *args, **keywords):
28+
def newfunc(*fargs, **fkeywords):
29+
newkeywords = keywords.copy()
30+
newkeywords.update(fkeywords)
31+
return func(*(args + fargs), **newkeywords)
32+
newfunc.func = func
33+
newfunc.args = args
34+
newfunc.keywords = keywords
35+
return newfunc
36+
\end{verbatim}
37+
38+
The \function{partial} is used for partial function application which
39+
``freezes'' some portion of a function's arguments and/or keywords
40+
resulting in an new object with a simplified signature. For example,
41+
\function{partial} can be used to create a callable that behaves like
42+
the \function{int} function where the \var{base} argument defaults to
43+
two:
44+
\begin{verbatim}
45+
>>> basetwo = partial(int, base=2)
46+
>>> basetwo('10010')
47+
18
48+
\end{verbatim}
49+
\end{funcdesc}
50+
51+
52+
53+
\subsection{\class{partial} Objects \label{partial-objects}}
54+
55+
56+
\class{partial} objects are callable objects created by \function{partial()}.
57+
They have three read-only attributes:
58+
59+
\begin{memberdesc}[callable]{func}{}
60+
A callable object or function. Calls to the \class{partial} object will
61+
be forwarded to \member{func} with new arguments and keywords.
62+
\end{memberdesc}
63+
64+
\begin{memberdesc}[tuple]{args}{}
65+
The leftmost positional arguments that will be prepended to the
66+
positional arguments provided to a \class{partial} object call.
67+
\end{memberdesc}
68+
69+
\begin{memberdesc}[dict]{keywords}{}
70+
The keyword arguments that will be supplied when the \class{partial} object
71+
is called.
72+
\end{memberdesc}

Lib/test/test_functional.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import functional
2+
import unittest
3+
from test import test_support
4+
5+
@staticmethod
6+
def PythonPartial(func, *args, **keywords):
7+
'Pure Python approximation of partial()'
8+
def newfunc(*fargs, **fkeywords):
9+
newkeywords = keywords.copy()
10+
newkeywords.update(fkeywords)
11+
return func(*(args + fargs), **newkeywords)
12+
newfunc.func = func
13+
newfunc.args = args
14+
newfunc.keywords = keywords
15+
return newfunc
16+
17+
def capture(*args, **kw):
18+
"""capture all positional and keyword arguments"""
19+
return args, kw
20+
21+
class TestPartial(unittest.TestCase):
22+
23+
thetype = functional.partial
24+
25+
def test_basic_examples(self):
26+
p = self.thetype(capture, 1, 2, a=10, b=20)
27+
self.assertEqual(p(3, 4, b=30, c=40),
28+
((1, 2, 3, 4), dict(a=10, b=30, c=40)))
29+
p = self.thetype(map, lambda x: x*10)
30+
self.assertEqual(p([1,2,3,4]), [10, 20, 30, 40])
31+
32+
def test_attributes(self):
33+
p = self.thetype(capture, 1, 2, a=10, b=20)
34+
# attributes should be readable
35+
self.assertEqual(p.func, capture)
36+
self.assertEqual(p.args, (1, 2))
37+
self.assertEqual(p.keywords, dict(a=10, b=20))
38+
# attributes should not be writable
39+
if not isinstance(self.thetype, type):
40+
return
41+
self.assertRaises(TypeError, setattr, p, 'func', map)
42+
self.assertRaises(TypeError, setattr, p, 'args', (1, 2))
43+
self.assertRaises(TypeError, setattr, p, 'keywords', dict(a=1, b=2))
44+
45+
def test_argument_checking(self):
46+
self.assertRaises(TypeError, self.thetype) # need at least a func arg
47+
try:
48+
self.thetype(2)()
49+
except TypeError:
50+
pass
51+
else:
52+
self.fail('First arg not checked for callability')
53+
54+
def test_protection_of_callers_dict_argument(self):
55+
# a caller's dictionary should not be altered by partial
56+
def func(a=10, b=20):
57+
return a
58+
d = {'a':3}
59+
p = self.thetype(func, a=5)
60+
self.assertEqual(p(**d), 3)
61+
self.assertEqual(d, {'a':3})
62+
p(b=7)
63+
self.assertEqual(d, {'a':3})
64+
65+
def test_arg_combinations(self):
66+
# exercise special code paths for zero args in either partial
67+
# object or the caller
68+
p = self.thetype(capture)
69+
self.assertEqual(p(), ((), {}))
70+
self.assertEqual(p(1,2), ((1,2), {}))
71+
p = self.thetype(capture, 1, 2)
72+
self.assertEqual(p(), ((1,2), {}))
73+
self.assertEqual(p(3,4), ((1,2,3,4), {}))
74+
75+
def test_kw_combinations(self):
76+
# exercise special code paths for no keyword args in
77+
# either the partial object or the caller
78+
p = self.thetype(capture)
79+
self.assertEqual(p(), ((), {}))
80+
self.assertEqual(p(a=1), ((), {'a':1}))
81+
p = self.thetype(capture, a=1)
82+
self.assertEqual(p(), ((), {'a':1}))
83+
self.assertEqual(p(b=2), ((), {'a':1, 'b':2}))
84+
# keyword args in the call override those in the partial object
85+
self.assertEqual(p(a=3, b=2), ((), {'a':3, 'b':2}))
86+
87+
def test_positional(self):
88+
# make sure positional arguments are captured correctly
89+
for args in [(), (0,), (0,1), (0,1,2), (0,1,2,3)]:
90+
p = self.thetype(capture, *args)
91+
expected = args + ('x',)
92+
got, empty = p('x')
93+
self.failUnless(expected == got and empty == {})
94+
95+
def test_keyword(self):
96+
# make sure keyword arguments are captured correctly
97+
for a in ['a', 0, None, 3.5]:
98+
p = self.thetype(capture, a=a)
99+
expected = {'a':a,'x':None}
100+
empty, got = p(x=None)
101+
self.failUnless(expected == got and empty == ())
102+
103+
def test_no_side_effects(self):
104+
# make sure there are no side effects that affect subsequent calls
105+
p = self.thetype(capture, 0, a=1)
106+
args1, kw1 = p(1, b=2)
107+
self.failUnless(args1 == (0,1) and kw1 == {'a':1,'b':2})
108+
args2, kw2 = p()
109+
self.failUnless(args2 == (0,) and kw2 == {'a':1})
110+
111+
def test_error_propagation(self):
112+
def f(x, y):
113+
x / y
114+
self.assertRaises(ZeroDivisionError, self.thetype(f, 1, 0))
115+
self.assertRaises(ZeroDivisionError, self.thetype(f, 1), 0)
116+
self.assertRaises(ZeroDivisionError, self.thetype(f), 1, 0)
117+
self.assertRaises(ZeroDivisionError, self.thetype(f, y=0), 1)
118+
119+
120+
class PartialSubclass(functional.partial):
121+
pass
122+
123+
class TestPartialSubclass(TestPartial):
124+
125+
thetype = PartialSubclass
126+
127+
128+
class TestPythonPartial(TestPartial):
129+
130+
thetype = PythonPartial
131+
132+
133+
134+
def test_main(verbose=None):
135+
import sys
136+
test_classes = (
137+
TestPartial,
138+
TestPartialSubclass,
139+
TestPythonPartial,
140+
)
141+
test_support.run_unittest(*test_classes)
142+
143+
# verify reference counting
144+
if verbose and hasattr(sys, "gettotalrefcount"):
145+
import gc
146+
counts = [None] * 5
147+
for i in xrange(len(counts)):
148+
test_support.run_unittest(*test_classes)
149+
gc.collect()
150+
counts[i] = sys.gettotalrefcount()
151+
print counts
152+
153+
if __name__ == '__main__':
154+
test_main(verbose=True)

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Core and builtins
3434
Extension Modules
3535
-----------------
3636

37+
- Added functional.partial(). See PEP309.
38+
3739
- Patch #1093585: raise a ValueError for negative history items in readline.
3840
{remove_history,replace_history}
3941

0 commit comments

Comments
 (0)