[Tutor] Passing functions as arguments to other functions

Steven D'Aprano steve at pearwood.info
Fri Sep 30 07:55:57 EDT 2016


On Thu, Sep 29, 2016 at 09:43:57PM -0500, boB Stepp wrote:

> I believe I understand the barebone mechanics on how to do this.  But
> I do not understand the rationale of why Python does it the way it
> does.  Say
> 
> def f(g, *args):
>     g(*args)

Right. This says:

- Define a function called "f", which takes one named parameter, "g",
  and collect any other arguments under the name "args".
- For the body of "f", simply call that argument "g" with all the other 
  arguments.

You probably want `return g(*args)` rather than just `g(*args)`.

 
> def g(*args):
>     # Do something.
> 
> do_things = f(g, *args)


As it stands now, f() is a middle-man that adds nothing to the picture. 
In practice, to be worth calling f() rather than just calling g() 
directly, you would want it to do some sort of additional processing: 
either manipulate the arguments, or the result returned by g().


> is the outline of how I understand the mechanics of doing this.  But
> noob boB initially want to do instead:
> 
> def f(g(*args)):
>     g(*args)
[...]
> which, of course, will give me a syntax error.

Of course. I'm not sure what you think a parameter called "g(*args)" 
even means.

When you write this:

def f(x):
    return x+1

do you feel the need to write:

def f(x+1):
    return x+1

instead? I expect not. Can you see how the two situations are 
equivalent? My example uses +1, your example uses "call the function 
with these arguments" instead, but the two are otherwise equivalent.

In common English, there is a simple name for the +1 part of x+1, 
namely "plus one" or "add one" or similar. But I don't know of any 
simple name for the `(*args)` part of `g(*args)`. "Call g with given 
arguments" perhaps, but that's not really simple. The situations are 
conceptually the same:

+ is a binary (two argument) operator that takes the object on the left, 
x, and the object on the right, 1, and adds them together;

() is an n-ary (n arguments) operator (or perhaps "pseudo-operator" if 
you prefer) that takes the object on the left, g, and the multiple 
arguments inside the brackets, *args, and applies the arguments to 
function g.


> Also, I note that if I just type a function name without the
> parentheses in the interpreter, I will get something like this:
> 
> >>> def f():
>            pass
> 
> >>> f
> <function f at 0x000001F775A97B70>
> 
> So the impression I am getting is that a function name by itself (with
> no parentheses) is the function *object*.

Correct.

> But why does Python require
> separating the function object from its parameters when it is being
> passed as an argument to another function?

If you pass the function arguments to the function *first*, then it gets 
evaluated before the second function gets to see it. Let's investigate:

def g(*args):
    print("Calling function g with arguments {}".format(args))
    return 999

def f(func, *args):
    print("f received first argument: {}".format(func))
    print("f received additional arguments: {}".format(args))
    return func(*args)


Let's try it the right way:

py> f(g, 1, 2, 3)
f received first argument: <function g at 0xb7ac965c>
f received additional arguments: (1, 2, 3)
Calling function g with arguments (1, 2, 3)
999



And the wrong way:

py> f(g(1, 2, 3))
Calling function g with arguments (1, 2, 3)
f received first argument: 999
f received additional arguments: ()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in f
TypeError: 'int' object is not callable



Does this help?



-- 
Steve


More information about the Tutor mailing list