Thursday, May 13, 2010

Partial With Frozen Arguments In Any Position

Recently, I was working on a problem where I wish I could have frozen arguments using a partial, but could not. The reason was that I needed to freeze the last argument instead of the first. Here's a simple example of what I wanted my function to behave like:
def contains_three(string):
  return '3' in string
But, I wanted to define it like so:
contains_three = functools.partial(operator.__contains__, '3')
But, partial only freezes the first arguments and you can't mix and match. I've ran into this before and I thought it would be nice to define a partial function that would allow you to freeze arguments in any position. Here's my implementation:
def custom_partial(func, *const_args, **const_kwds):
    def result(*args,**kwds):
        args_iter=iter(args)
        def convert(value):
            if value is None:
                return args_iter.next()
            else:
                return value
        to_call = map(convert, const_args)
        new_kwds = const_kwds.copy()
        new_kwds.update(kwds)
        return func(*(to_call + list(args_iter)), **new_kwds)
    return result
Is there a test? Of course, there is...
from operator import __contains__
import unittest
class Test(unittest.TestCase):
    def testContains(self):
        has_three = custom_partial(__contains__, None, '3')
        self.assertTrue(has_three('1234'))
        self.assertFalse(has_three('124'))
By looking at the test and code, the implementation I chose was to have the value of None to denote any argument I don't want to freeze. I could have created a placeholder value for it, but decided since I haven't had a need to freeze an argument with None to just leave it.

Now, I'm not excited about the implementation, but it satisfies my initial requirements. I'm going to continue to work on this to come up with something more elegant, but for now it works.