In order to get a better feeling for what can be done with functools.partial() I am taking some “real world” python code of mine and refactoring it to use curried functions.
In the example below the construct_getopt_data() function (lines 7-17) takes the data structure shown on lines 44-54 (below) and returns a 2-tuple where
- the first element is a string of option letters (second argument to getopt() (see variable shortflags in the output below)) and
- the second element is a list of strings with the names of long options (third argument to getopt() (see variable longflags below))
The resulting data is thus as follows:
mhr@playground2:~/src/published$ python2.5 partialf.py
shortflags = 'l:ep:a:x:r:o:i:dc'
longflags =
( 'lines=',
'echo',
'fyo',
'pager=',
'pgr=',
'algo=',
'recipient=',
'decrypt',
'crypt')
>> test ok
And the code that’s producing the output above looks as follows:
1 #!/usr/bin/env python
2 from pprint import PrettyPrinter as PP
3 from itertools import (imap, repeat)
4 from functools import partial
5 from operator import (ge, lt)
6
7 def construct_getopt_data(args):
8 """uses lambdas"""
9 # single and multi-character flag iterators
10 shortiter = lambda args: argiter(args, lambda s: s <= 2)
11 longiter = lambda args: argiter(args, lambda s: s > 2)
12
13 # single character flags
14 shortfs = imap(formatf, shortiter(args), repeat(':'))
15 # multi-character flags
16 longfs = imap(formatf, longiter(args), repeat('='))
17 return(''.join(shortfs), tuple(longfs))
18
Please note:
The analogous function construct_getopt_data2() (lines 19-29) below performs the same taks but uses curried functions as opposed to lambdas.
19 def construct_getopt_data2(args):
20 """uses functools.partial"""
21 # single and multi-character flag iterators
22 shortiter = partial(argiter, op=partial(ge, 2))
23 longiter = partial(argiter, op=partial(lt, 2))
24
25 # single character flags
26 shortfs = map(partial(formatf, fchar=':'), shortiter(args))
27 # multi-character flags
28 longfs = map(partial(formatf, fchar='='), longiter(args))
29 return(''.join(shortfs), tuple(longfs))
30
It utilises functools.partial()
- on lines 22-23: to customise the argument iterator by
- currying the built-in operator functions operator.ge() (greater or equal) and operator.lt() (less than)
- using these curried operator functions to preset the op parameter of the argiter() generator
- on lines 26 and 28 to preset the fchar parameter of the formatf() function (the ruse with itertools.repeat() is hence not needed any more)
31 def argiter(args, op):
32 """pair short/long flags will their respective types"""
33 for flags, argdata in args.iteritems():
34 for flag in flags:
35 if op(len(flag)): yield (flag, argdata[1])
36
The argiter() generator above facilitates the iteration over the input data structure (lines 44-54) in (single character flag, type) and (multi-character parameter, type) pairs respectively.
37 def formatf((argn, argt), fchar):
38 """format for getopt(),
39 argn is the flag, argt is its type, fchar is one of ':' or '='"""
40 return argt == bool and argn.lstrip('-') or "%s%s" % (argn.lstrip('-'), fchar)
41
The formatf() function above returns the single and multi-character command line flags in the format required by getopt().
42 if __name__ == '__main__':
43 # dictionary with command line args along with their types and defaults
44 args = {
45 ('-a', '-x', '--algo') : ('algo', str, None),
46 ('-c', '--crypt') : ('crypt', bool, None),
47 ('-d', '--decrypt') : ('decrypt', bool, None),
48 ('-e', '--echo', '--fyo') : ('echo', bool, None),
49 ('-l', '--lines') : ('lines', int, '25'),
50 ('-i', ) : ('input', str, None),
51 ('-o', ) : ('output', str, None),
52 ('-p', '--pager', '--pgr') : ('pager', str, '/usr/bin/less'),
53 ('-r', '--recipient') : ('recipient', str, None)
54 }
55 pp = PP(indent=4)
56 sfs1, lfs1 = construct_getopt_data(args)
57 sfs2, lfs2 = construct_getopt_data2(args)
58 print "shortflags =", pp.pformat(sfs1)
59 print "longflags =\\n", pp.pformat(lfs1)
60
61 if (sfs1 == sfs2) and (lfs1 == lfs2): print "\\n>> test ok"
62 else: print "\\n>> test failed"
In conclusion
While functools.partial() is certainly an interesting and cool addition to the python toolchest it would appear that it’s not indispensible for functional style programming.
I would love to see examples or code snippets that are made possible and/or improved greatly by leveraging functools.partial().