1

I tried this code from a Python tutorial:

def func1(a):
    return a ** a
def func2(a):
    return func1(a) * func1(a)
print(func2(2))

It displays 16, and I am trying to understand how this works.

Does func1 get called when the return statement starts to run?

Can return call functions?

I tried to understand how it works by adding a print:

def func1(a):
    print("Hello World")
    return a ** a
def func2(a):
    return func1(a) * func1(a)
print(func2(2))

I see that the Hello World message is printed two times, so I assume that func1 is getting called twice. How exactly does this work? Is the * in this line related to how it works?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Sendel
  • 21
  • 4
  • 7
    Return isn't doing anything. `func1(a) * func1(a)` is an expression that gets evaluated to some value *first*. That value is then returned. – juanpa.arrivillaga Jan 22 '23 at 01:37
  • Is the confusion related what the "*" does? func1(2) returns 2^2=4 since ** is an exponential. so func1(2)*func1(2)=4*4=16. Then the return on func2 returns this value. Can you clarify any points of confusion? – DrRaspberry Jan 25 '23 at 04:36
  • Possibly related: https://stackoverflow.com/questions/57518639/ – Karl Knechtel Jan 25 '23 at 05:55

2 Answers2

2

Expressions

When code says something like func1(2), this is an expression. The term "expression" comes from mathematics. Specifically, this expression is a call of the function func1, passing it an argument 2. When the function is called, it will receive the value 2, assigning it to the a parameter (i.e., naming it a).

Expressions can be simpler than that. An individual variable name by itself is also an expression. It simply evaluates to whatever that variable is naming, in the current context. So is a literal value, like 2. It evaluates, unsurprisingly, to itself.

Expressions can also be more complex than that. They can be composed of other expressions. So we can write 1 + 2 * 3, and it is evaluated (after figuring out the operator precedence) by computing 2 * 3 to get 6, and then 1 + 6 to get 7. The result from evaluating each step, substitutes in to the next step. The same thing happens with calls to functions. We can write, for example, func1(func1(2)); the inner func1(2) is evaluated to 4, and so the next step is to call func1(4), resulting in 256. Or we can write something like func1(2) * func1(2); the calls happen first, evaluating to 4 each time, and then 4 * 4 results in 16.

But why did the calls evaluate that way? Because of the return statements in the functions they called. So now we also need to understand statements.

Statements

An expression is one kind of statement in Python. Statements are the individual steps in a running program - normally, a single line of code.

The point is, we can write an expression on a line by itself in Python, and that is one kind of statement. The result is computed, and then thrown away. (How is this useful? Because the expression could have side effects, for example, assigning to a global variable, or raising an exception. Python is so flexible that, most of the time, it would be necessary to actually evaluate an expression in order to figure out whether it could have a side effect. So, Python doesn't forbid writing code like that, and even "obviously" useless things like 1 + 1 on a line by itself are allowed. (There is, however, one special case: docstrings are given special treatment.))

Another important statement in this code is the return statement. This consists of the keyword return statement, optionally followed by... an expression.

The purpose of the return statement is to state the result of evaluating the call. return means: "evaluate this expression, and then exit the current function. The result of evaluating the expression is the result of evaluating the function call, so there is no more work to do in this function." (When it appears by itself, return in Python means the same thing as return None, which also happens if the function reaches the end without a return statement - that is to say, in those cases, the function call evaluates to special value None.)

So: in func2, return func1(a) * func1(a) means that func1(a) * func1(a) will be evaluated, resulting in 16 - because func1(a) was evaluated twice, and the result both times was 4, and 4 * 4 gives 16. Thus, that is the result of evaluating the function call.

(But why did func1(a) evaluate to 4? By the same reasoning. Each time, func1 was given a value of 2 - func2's value named a - for its own a. It computed a ** a, i.e., 2 ** 2, getting 4, and returned that.)

At the top level, print(func2(2)) will therefore call func2(2) first, evaluating to 16, and then pass 16 to print (in Python 3.x, print is a function). print will display the text as a side effect (remember the bit above about side effects?) and return None, which is then ignored (because this statement in the code is just an expression by itself). (In 2.x, print was a statement, with its own special syntax. This accomplished a similar goal, but with a lot of subtle differences.)

So, pedantically, no - return doesn't "call" other functions; functions are called by... calling them. return can be followed by an expression, and its job is to report (in other words... return) the result of that expression, from the current function, to the place where it was called. But the expression written after return is just an expression, following the normal rules; so it can contain more calls to other functions. (Or even the same function again - this is called recursion, and is the basis of some powerful techniques.)

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
1

return is executed last. The expression will be evaluated first.

func1(2) returns 2 ** 2, which is 4.
It is getting called twice, which is why Hello World is being printed twice.

4 * 4 is 16.

Now that the expression is evaluated, func2 will now return 16.

return is a keyword that stops the function(and returns the value you put after), so everything else in that line will have to be executed first.

Shiv
  • 370
  • 1
  • 14