Quanjiang Liu
Quanjiang Liu

Reputation: 9

Why is the environment diagram in the following Python code inconsistent with its execution order?

When I was studying the SICP course, the execution order of the following code in the environment diagram is inconsistent with what I expected.

def add_one(x):
    y = x+1
    return y
def square(x):
    return x*x
    
square(add_one(9))

I am expecting the following evaluation order:

  1. Evaluate the operator square. A function value is returned.
  2. Evaluate the operand. Now we have to evaluate another expression add_one(9).
  3. Evaluate the operator add_one. A function value is returned.
  4. Evaluate the operand 9. Now the expression add_one(9) returns 10
  5. The whole expression square(add_one(9)) return 100.

However, I run the code on this website It told me that the add_one frame created before the square, which is opposite of what I expected. Is it right?

I asked ChatGPT about this question. It gives the answer that this is because in Python, the priority of function calls is higher than that of operators.

Upvotes: 0

Views: 74

Answers (3)

Frank Yellin
Frank Yellin

Reputation: 11307

Your order is evaluation is mostly correct. However you are confusing "creation of the frame" with "evaluation of the frame".

To call a function, Python evaluates the function and its arguments. This order appears to be left-to-right, but I cannot find anything in the documentation that guarantees this. [Other StackOverflow answers say it is guaranteed left-to-right, so maybe there's something I'm missing.]

Only after the function and its arguments are evaluated is the function call actually made. This is when the frame is created.

Try the following code. It will give you a clearer idea of evaluation order.

def add_onex():
    print("Calling add_one")
    return add_one

def squarex():
    print("Evaluating square")
    return square

def add_one(x):
    print("Calling add one")
    y = x + 1
    return y

def square(x):
    print("Calling square")
    return x * x

result = squarex()(add_onex()(9))

Obviously, in most cases, the evaluation of a function is a simple name lookup. But in cases where evaluation of the function is not trivial, you can see that this evaluation happens before the arguments are evaluated.

Upvotes: -1

Hugo G
Hugo G

Reputation: 16526

The evaluation order will be as follows:

  • add_one(9) is executed, as it's the innermost nested expression
  • the return value of 10 is calculated and returned from function
  • square function is invoked with 10 as its parameter
  • the return value of 100 is calculated and returned from function
  • as this is the outermost function call, the expression results in 100

The big difference to your list is the order in which the functions are executed. The innermost is usually* executed first. You can think of the brackets (parentheses) that we use to invoke functions as another layer of operator priority. Things within brackets are run before things outside brackets.

While GPT is right about functions being invoked before operators, here the question is more about which function gets invoked first. Once a function is invoked, it doesn't matter what it or other functions contain - the whole function is being run using the same rules, but applied to the function body only. E.g. functions called within an expression in the function would be called before operators in the same expression.

* = the only exception being latent functions like lambdas, in which case the parameter itself is of type function. You will get to this a bit later in your studies.

Upvotes: 2

Scooter
Scooter

Reputation: 7059

You can't evaluate square() until you can provide it with the argument value. So add_one() has to be evaluated first so the result can be passed to square().

Upvotes: 2

Related Questions