Program execution

We’ll now start to get a little more understanding about how Python interprets code. From some of the activities we’ve done in class, you may be starting to realize that programming requires giving instructions AND thinking about how those instructions will be received and carried out. Being able to take on the “perspective” of the computer by forming a mental model of how Python interprets code will help you as you write and debug your programs.

Tracing a program

How does Python interpret your code? We can use the Python Tutor tool to help us. Once we've entered the code we want to trace, we can click the Visualize Execution button. This will change the view around the panel containing your code. Below the panel, there is a next button. Click next and the first line of code is executed. On the right side of the panel, you will see an area called “Global frame”. Python Tutor will show your variables and their values there as you execute the code one line at a time, by repeatedly clicking the Next button.

Example: variables

Here's a simple Python Tutor example that traces how variables are stored in memory.

# assign a variable named width the value 2

width:int = 2


# assign a variable named height the value 3

height:int = 3


# calculate the area of a rectangle with that size

area:int = width * height

Program control

"Program control" or "control flow" refers to the order in which our program's code is executed. We tend to think of it executing line by line, which is a good rule of thumb. However, control structures, such as conditionals and loops, can make the program control "hop" from one line to another.

Let's take a look at this example.

a:int = 3

if a > 5:

print( "Hi!" )

print( "I'm done with conditionals." )

Function execution frames

Function invocations also impact the flow of a program.

Example: simple function

Python Tutor can help us understand how functions are executed.

# Define the hello world function

def helloWorld() -> None:

print ("Hello, world!")


helloWorld()

print("Hi back!")

The first time that we click Next, the helloWorld function is defined. Notice the entry in Global frame for helloWorld, pointing to a function. You can think of this like adding an entry to a dictionary or glossary. Clicking Next now calls the helloWorld function. When a function invocation is encountered, an execution frame is created. Notice that CodeLens shows this new Frame on the right side, and the arrow keeping track of what will be executed next is pointing at the helloWorld function again. However, this time we will execute the function, not just define it. Click Next again, and you should see the execution arrow now pointing to the print function call that is inside helloWorld. One more Next click will result in the output being displayed in the top right. Click Next again, and the helloWorld function returns, getting ready to execute the print function call on line 6.

Notice how execution moved into the function when it was called, and moved back after the function call completed. We refer to how execution moves as control flow. The control moves into the function when the function is executed and moves back to where it is called when the function returns.

Things get more interesting when the function has a parameter. In this case, the execution frame is like a temporary workspace for everything related to that function call. This includes binding the arguments passed in the invocation to the parameter names before executing the body of the function. Binding the arguments to the parameters means that the parameters get the values that the arguments have.

Let’s look at this next example which has a parameter. Notice the frame labeled “square” representing the execution of the square function. Notice how x gets set differently each time that it is called, depending on the argument that is passed in.

The video refers to the Python Tutor tool as "CodeLens".

Here is another example to gain more insight into what occurs when your code is executed. When Python encounters a function def block, it associates the function identifier to the body of the function. You can think of this like adding an entry to a dictionary or glossary.

When a function invocation is encountered, an execution frame is established. You can think of this like a temporary workspace for everything related to that function call. This includes associated the arguments passed in the invocation to the parameter names before executing the body of the function (which is “looked up” in the dictionary).

Try using Python Tutor with the same code from the video!

def square( x:int ) -> int:

"""Takes in a number x and returns the square of x."""

return x*x


n:int = 2 # assign a variable named n the value 2


# invoke the function square on the variable n

squaredValue:int = square( n )


# invoke the function square on the squaredValue

squaredValue = square( squaredValue )

Now we’ll use Python Tutor to see what happens when a function is invoked from a function body. Notice how each time a function is called, a new frame is created and all the work is done in that frame until it calls another function or it returns. When squaredSquare calls square, we have 3 frames: the global frame, the frame for squaredSquare, and the frame for square. When square returns, its frame goes away, leaving us with the global frame and the squaredSquare frame.

def square( x:int ) -> int:

"""Takes in a number x and returns the square of x."""

return x*x


def squaredSquare( x:int ) -> int:

"""Takes in a number x and returns the x^4."""

return square(x)*square(x)


n:int = 2 # assign a variable named n the value 2


# invoke the function squaredSquare on the variable n

squaredValue:int = squaredSquare( n )


print( squaredValue )

Primitive vs Reference types

Primitive types

  • int

  • float

  • bool

Reference types

  • str

  • list

A function cannot modify a parameter that is a primitive type

Try this in Python Tutor!

def fun1() -> None:

number:int = 2

fun2( number )

def fun2( number:int ) -> None:

number = 20


fun1()

A function can modify a parameter that is a reference type

Try this in Python Tutor!

def fun1() -> None:

numbers:list = [1,2,3]

fun2( numbers )

def fun2( nums:list ) -> None:

nums[1] = 0


fun1()