🚰How Does Control Flow?

Control flows downwards... mostly.

Direct Control Flow

In the simplest of functions, control simply flows downward.

a = 3
print(a) # prints 3

The reason this works is because the control goes up to down –– 3 is bound to a, and then looked up in line 2. This is quite like English, so we do it intuitively.

Breaking Control Flow With If

The first way you learn how to break control is through if. If is known as the "conditional", because one of many statements is conditionally executed.

if (3 > 4):
    print("suite 1") # this does not get executed
else:
    print("suite 2") # this gets executed

Try it out yourself in PythonTutor.

As you can see, the control arrow jumps from line 1 to line 3, skipping line 2 entirely. You could add anything to that line, and it won't error –– because it will never execute.

if (3 > 4):
    print(1/0) # should throw error but never executed
else:
    print("suite 2") # this gets executed

Breaking Control Flow With While

Another way we could change the flow of control is to make it go over the same block of code over and over again.

n = 1
while (n < 4):
    print("Hello") # prints thrice
    n = n + 1

The fundamental idea behind the loop is to do the same thing over and over again with small changes each time.

There are four parts to the loop –– the initial assignment (n = 1), the iterative condition (while n < 4), the execution (printing Hello) and the updation (n = n + 1). When we combine all four of these, we get a block of code that operates iteratively.

Breaking Flow Of Control With Functions

Another, more complicated way, the control flow is broken is through functions. Take this simple example.

def f(x):
    print("Inside the function") # doesn't get executed
    return 1/0 # does not error

print("Outside the function") # gets executed

Try it out yourself in Python Tutor

So the function signature (that's the def line) is read, but not the function itself. The error inside the function doesn't execute, nor is the print statement. While the function's name and number of arguments are read and saved, the body of the function is not.

Now let's think about what happens when you call the function.

def f(x):
    print(a) # is executed
    return x+1

a = 3
print(f(a)) # prints 4
print(a) # prints 3

First, we bind a function with one argument to f. Then, we bind a to 3. Then, we call f with a (which is 3).

This call takes you out of your "main" control flow, and creates a separate flow. You've previously thought about this in terms of a new frame.

In this frame, everything inside of the frame is accessible, and everything in all its parent frames is also accessible. Moreover, anything that gets passed into this frame is a new copy –– the x inside of the function is not the same as the a that is passed in (This idea will grow in importance as the things we pass in get more and more complicated).

def f(x):
    x = x+1
    print(a) # prints 3
    return x

a = 3
print(f(a)) # prints 4
print(a) # prints 3

As seen, the value a is accessible despite not being in the function's flow, because it is in the parent flow. Of course, x itself is accessible. Changing x does not change a, because x is an independent copy of a.

The return value ends the function's frame and leads you back into the main flow ("global frame"). Even functions that don't explicitly have a return value have an implicit return None statement.

When the function returns to its own frame, it brings a value with it, and normal control flow resumes.

Frames Are Independent Of Each Other

Consider the following example:

def f(x):
    a = 4
    return x

a = 3
print(f(a)) # Print 1
print(a) # Print 2

What do you think will be printed? Take a moment to try it out in PythonTutor.

You'll notice that Print 2 will return 3! So what happened to the a=4 that we defined. This is the idea of framing. The universe that f(x) operates in is different (but not completely divorced) from the universe of the main program (the global universe, if I may). The a that is defined in the function is completely different from the a that is defined before the function call. That's right –– there are two different a s existing!

This is what I mean by "the independence of frames". That is to say, a different frame has its own defined variables that may have the same name as a parent frame.

You may feel like this idea is starting to get familiar –– and it rightly should–– it's the idea behind environment diagrams! I think understanding this concept makes environment diagrams a lot more simple. Check out the quick note on those to understand how to use these concepts!

Last updated