Tracing Function Calls in the Memory Model

Read the following code. Can you predict what it will do when we run it?

 >>>​​ ​​def​​ ​​f(x):
 ...​​ ​​x​​ ​​=​​ ​​2​​ ​​*​​ ​​x
 ...​​ ​​return​​ ​​x
 ...
 >>>​​ ​​x​​ ​​=​​ ​​1
 >>>​​ ​​x​​ ​​=​​ ​​f(x​​ ​​+​​ ​​1)​​ ​​+​​ ​​f(x​​ ​​+​​ ​​2)

That code is confusing, in large part because x is used all over the place. However, it is pretty short and it only uses Python features that we have seen so far: assignment statements, expressions, function definitions, and function calls. We’re missing some information: Are all the x’s the same variable? Does Python make a new x for each assignment? For each function call? For each function definition?

Here’s the answer: whenever Python executes a function call, it creates a namespace (literally, a space for names) in which to store local variables for that call. You can think of a namespace as a scrap piece of paper; Python writes down the local variables on that piece of paper, keeps track of them as long as the function is being executed, and throws that paper away when the function returns.

Separately, Python keeps another namespace for variables created in the shell. That means that the x that is a parameter of function f is a different variable than the x in the shell!

Let’s refine our rules from Functions That Python Provides, for executing a function call to include this namespace creation:

  1. Evaluate the arguments left to right.

  2. Create a namespace to hold the function call’s local variables, including the parameters.

  3. Pass the resulting argument values into the function by assigning them to the parameters.

  4. Execute the function body. As before, when a return statement is executed, execution of the body terminates and the value of the expression in the return statement is used as the value of the function call.

From now on in our memory model, we will draw a separate box for each namespace to indicate that the variables inside it are in a separate area of computer memory. The programming world calls this box a frame. We separate the frames from the objects by a vertical dotted line:

images/functions/trace1.png

Using our newfound knowledge, let’s trace that confusing code. At the beginning, no variables have been created; Python is about to execute the function definition. We have indicated this with an arrow:

»>>>​​ ​​def​​ ​​f(x):
 ...​​ ​​x​​ ​​=​​ ​​2​​ ​​*​​ ​​x
 ...​​ ​​return​​ ​​x
 ...
 >>>​​ ​​x​​ ​​=​​ ​​1
 >>>​​ ​​x​​ ​​=​​ ​​f(x​​ ​​+​​ ​​1)​​ ​​+​​ ​​f(x​​ ​​+​​ ​​2)

As you’ve seen in this chapter, when Python executes that function definition, it creates a variable f in the frame for the shell’s namespace plus a function object. (Python didn’t execute the body of the function; that won’t happen until the function is called.) Here is the result:

images/functions/trace2.png

Now we are about to execute the first assignment to x in the shell.

 >>>​​ ​​def​​ ​​f(x):
 ...​​ ​​x​​ ​​=​​ ​​2​​ ​​*​​ ​​x
 ...​​ ​​return​​ ​​x
 ...
»>>>​​ ​​x​​ ​​=​​ ​​1
 >>>​​ ​​x​​ ​​=​​ ​​f(x​​ ​​+​​ ​​1)​​ ​​+​​ ​​f(x​​ ​​+​​ ​​2)

Once that assignment happens, both f and x are in the frame for the shell:

images/functions/trace3.png

Now we are about to execute the second assignment to x in the shell:

 >>>​​ ​​def​​ ​​f(x):
 ...​​ ​​x​​ ​​=​​ ​​2​​ ​​*​​ ​​x
 ...​​ ​​return​​ ​​x
 ...
 >>>​​ ​​x​​ ​​=​​ ​​1
»>>>​​ ​​x​​ ​​=​​ ​​f(x​​ ​​+​​ ​​1)​​ ​​+​​ ​​f(x​​ ​​+​​ ​​2)

Following the rules for executing an assignment from Assignment Statement, we first evaluate the expression on the right of the =, which is f(x + 1) + f(x + 2). Python evaluates the left function call first: f(x + 1).

Following the rules for executing a function call, Python evaluates the argument, x + 1. In order to find the value for x, Python looks in the current frame. The current frame is the frame for the shell, and its variable x refers to 1, so x + 1 evaluates to 2.

Now we have evaluated the argument to f. The next step is to create a namespace for the function call. We draw a frame, write in parameter x, and assign 2 to that parameter:

images/functions/trace4.png

Notice that there are two variables called x, and they refer to different values. Python will always look in the current frame, which we will draw with a thicker border.

We are now about to execute the first statement of function f:

 >>>​​ ​​def​​ ​​f(x):
»...​​ ​​x​​ ​​=​​ ​​2​​ ​​*​​ ​​x
 ...​​ ​​return​​ ​​x
 ...
 >>>​​ ​​x​​ ​​=​​ ​​1
 >>>​​ ​​x​​ ​​=​​ ​​f(x​​ ​​+​​ ​​1)​​ ​​+​​ ​​f(x​​ ​​+​​ ​​2)

x = 2 * x is an assignment statement. The right side is the expression 2 * x. Python looks up the value of x in the current frame and finds 2, so that expression evaluates to 4. Python finishes executing that assignment statement by making x refer to that 4:

images/functions/trace5.png

We are now about to execute the second statement of function f:

 >>>​​ ​​def​​ ​​f(x):
 ...​​ ​​x​​ ​​=​​ ​​2​​ ​​*​​ ​​x
»...​​ ​​return​​ ​​x
 ...
 >>>​​ ​​x​​ ​​=​​ ​​1
 >>>​​ ​​x​​ ​​=​​ ​​f(x​​ ​​+​​ ​​1)​​ ​​+​​ ​​f(x​​ ​​+​​ ​​2)

This is a return statement, so we evaluate the expression, which is simply x. Python looks up the value for x in the current frame and finds 4, so that is the return value:

images/functions/trace6.png

When the function returns, Python comes back to this expression: f(x + 1) + f(x + 2). Python just finished executing f(x + 1), which produced the value 4. It then executes the right function call: f(x + 2).

Following the rules for executing a function call, Python evaluates the argument, x + 2. In order to find the value for x, Python looks in the current frame. The call on function f has returned, so that frame is erased: the only frame left is the frame for the shell, and its variable x still refers to 1, so x + 2 evaluates to 3.

Now we have evaluated the argument to f. The next step is to create a namespace for the function call. We draw a frame, write in the parameter x, and assign 3 to that parameter:

images/functions/trace7.png

Again, we have two variables called x.

We are now about to execute the first statement of function f:

 >>>​​ ​​def​​ ​​f(x):
»...​​ ​​x​​ ​​=​​ ​​2​​ ​​*​​ ​​x
 ...​​ ​​return​​ ​​x
 ...
 >>>​​ ​​x​​ ​​=​​ ​​1
 >>>​​ ​​x​​ ​​=​​ ​​f(x​​ ​​+​​ ​​1)​​ ​​+​​ ​​f(x​​ ​​+​​ ​​2)

x = 2 * x is an assignment statement. The right side is the expression 2 * x. Python looks up the value of x in the current frame and finds 3, so that expression evaluates to 6. Python finished executing that assignment statement by making x refer to that 6:

images/functions/trace8.png

We are now about to execute the second statement of function f:

 >>>​​ ​​def​​ ​​f(x):
 ...​​ ​​x​​ ​​=​​ ​​2​​ ​​*​​ ​​x
»...​​ ​​return​​ ​​x
 ...
 >>>​​ ​​x​​ ​​=​​ ​​1
 >>>​​ ​​x​​ ​​=​​ ​​f(x​​ ​​+​​ ​​1)​​ ​​+​​ ​​f(x​​ ​​+​​ ​​2)

This is a return statement, so we evaluate the expression, which is simply x. Python looks up the value for x in the current frame and finds 6, so that is the return value (as shown in the figure).

images/functions/trace9.png

When the function returns, Python comes back to this expression: f(x + 1) + f(x + 2). Python just finished executing f(x + 2), which produced the value 6. Both function calls have been executed, so Python applies the + operator to 4 and 6, giving us 10.

We have now evaluated the right side of the assignment statement; Python completes it by making the variable on the left side, x, refer to 10:

images/functions/trace10.png

Phew! That’s a lot to keep track of. Python does all that bookkeeping for us, but to become a good programmer it’s important to understand each individual step.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.68