7

Say I have a two dimensional function f(x,y) and another function G(function) that takes a function as an input. BUT, G only takes one dimensional functions as input and I'm wanting to pass f to G with the second variable as a fixed parameter.

Right now, I am just declaring a third function h that sets y to a set value. This is what it looks like in some form:

def f(x,y):
   something something something
   return z;

def G(f):
    something something something

def h(x):
   c= something
   return f(x,c);
G(h)

At some point I was also making y a default parameter that I would change each time.

Neither of these are as readable as if I was somehow able to call

G(f(x,c))

that particular syntax doesn't work. What is the best way to do this?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
albiero
  • 81
  • 1
  • 1
  • 7
  • 1
    currying and partial application are good topics to research further if you are interested in functional programming. For starters see http://stackoverflow.com/questions/1352855/in-functional-programming-what-is-currying and http://stackoverflow.com/questions/218025/what-is-the-difference-between-currying-and-partial-application – roippi Feb 25 '14 at 23:47
  • Does this answer your question? [Function restriction by fixing an argument](https://stackoverflow.com/questions/17159065/function-restriction-by-fixing-an-argument) – mkrieger1 Jul 24 '22 at 19:04

4 Answers4

8

An ideal solution would use partial application, but the quickest and easiest way to accomplish this would be to wrap f inside a lambda statement like this:

G(lambda x: F(x, C))

In this example, the lambda syntax creates an anonymous function that accepts one argument, x, and calls f with that value x and the constant C. This works because the value of C is "captured" when the lambda is created and it becomes a local constant inside the lambda.

ApproachingDarknessFish
  • 14,133
  • 7
  • 40
  • 79
7

The functools.partial function can be used to do this (note, it's not entirely clear where c comes from in your example code, so I've assumed it's some constant).

import functools

def f(x,y):
    return x+y

c = 3

G = functools.partial(f, c)
G(4)

I think this is more explicit than the lambda approaches suggested so far.

Edit: replacing the right most argument is not possible as we are dealing with positional arguments. Depending on the level of control available, you could introduce a wrapper which handles the switching:

import functools

def f(x,y):
    return x+y

def h(c,y):
    return f(y,c)

c = 3

G = functools.partial(h, c)
G(4)

But I think you start to sacrifice readability and maintainability at this point...

Neuron
  • 5,141
  • 5
  • 38
  • 59
Mark Streatfield
  • 3,189
  • 1
  • 22
  • 19
  • +1. Agreed, this is definitely better than my answer. – ApproachingDarknessFish Feb 25 '14 at 23:49
  • As partial is dealing with positional arguments, I don't believe this is possible. Switching to keyword arguments (which partial also supports) would be one workaround. The partial object (http://docs.python.org/2/library/functools.html#partial-objects) does expose the arguments so you could maybe manipulate those. Depending how much control you have, you could create some intermediate function which does the swapping (see edit). But all of this would begin to be at the cost of code readability and maintainability. – Mark Streatfield Feb 26 '14 at 02:36
7

What you are looking for is called a closure.

def make_h(c):
   def h(x):
       return f(x, c)
   return h

Now if you assign h = make_h(c), then h(x) equals f(x, c), and you can pass your h to G.

If you wish, the functools library has support for closures (functools.partial)

chryss
  • 7,459
  • 37
  • 46
1

Try this:

def h():
    return lambda x: f(x,c)

No need to supply the x to h - you could pass a function to wrap eventually if it wouldn't be in the scope. In fact, h is obsolete unless you want it to work with any 2-argument function:

def h(functionToWrap, fixedSecondArgument):
     return lambda x: functionToWrap(x, fixedSecondArgument)
BartoszKP
  • 34,786
  • 15
  • 102
  • 130