Python, while being a fully object-oriented language, has a fairly rich set of functional programming tools too. I'm discovering more and more of them as a I go along, continually being surprised at how neatly functional programming solves some of the common problems I've seen.
What I was trying to do was create a file change handler that ran on a separate thread and would be called when files were modified. The first attempt looked a bit like this:
def test(args): # run all the tests def watch(args): watch_and_run(['*.py'], test, args) def watch_and_run(patterns, function, argument): handler = FileChangeHandler(patterns, function, argument) watchdog.run(handler)
FileChangeHandler was written that was because it was going to run the
test method in a separate thread, like this:
class FileChangeHandler(PatternMatchingEventHandler): def __init__(self, function, arguments, *args, **kwargs): self._function = function self._argument = argument super(FileChangeTestRunner, self).__init__(*args, **kwargs) def on_modified(self, event): self._function(self._argument)
The problems with this approach are fairly obvious and show themselves quickly:
An elegant solution, in cases like this, is currying. This basically involves creating a new function that calls the function you want to call with the arguments you want to pass in.
Here's the new code using
class FileChangeHandler(PatternMatchingEventHandler): def __init__(self, handler, *args, **kwargs): self._handler = handler super(FileChangeTestRunner, self).__init__(*args, **kwargs) def on_modified(self, event): self._handler() def watch(args): watch_and_run(['*.py'], functools.partial(test, args))
What's happening here is that the
watch_and_run is being passed the output of
functools.partial, which is a function that, when run, will call
args as the argument. Here's how it works (from the documentation):
def partial(func, *args, **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
It defines a
newfunc, which when called updates the original
kwargs that you pass in with the ones that it is called with. It then calls the original function with these updated arguments (and stores the original values as properties that you can access).