Skip to main content

Chapter 4 - Functions (Python)

Here's a concise walkthrough of the main ideas in Chapter 4, each with a small example.


Defining and calling functions

A function is a mini-program defined with def and run later by calling its name with (). Functions help you avoid duplicated code.

Example:

def hello():
print("Good morning!")
print("Good afternoon!")
print("Good evening!")

hello()
hello()

Arguments and parameters

Parameters are variables in the function definition; arguments are the actual values you pass in when calling.

Example:

def say_hello_to(name):  # name is a parameter
print("Good morning,", name)

say_hello_to("Alice") # "Alice" is an argument
say_hello_to("Bob")

After the function returns, name no longer exists outside the function.


Return values and return

A function call evaluates to its return value, which you specify with return. If no return is used, the function returns None.

Example:

def square(x):
return x * x

result = square(5) # 25
print(result)
print(square(3) + square(4)) # 25

Magic 8-ball style function

You can use return plus random to produce different answers depending on a number.

Example:

import random

def get_answer(n):
if n == 1:
return "It is certain"
elif n == 2:
return "Ask again later"
else:
return "Very doubtful"

print(get_answer(random.randint(1, 3)))

The None value

None represents "no value"; it is the single value of type NoneType and is written with a capital N. Functions with no explicit return effectively return None.

Example:

spam = print("Hello!")  # prints Hello!
print(spam is None) # True

Also, return by itself returns None:

def do_nothing():
return

print(do_nothing() is None) # True

Named (keyword) parameters in built-ins

Some functions (like print) have named parameters such as end and sep, which you pass as name=value to customize behaviour.

Example (end):

print("Hello", end="")
print("World") # HelloWorld

Example (sep):

print("cats", "dogs", "mice", sep=",")
# cats,dogs,mice

The call stack

Python tracks where to return after each function call using a call stack. Each call adds a frame; returning pops that frame and resumes after the call.

Example:

def c():
print("c() starts")
print("c() returns")

def b():
print("b() starts")
c()
print("b() returns")

def a():
print("a() starts")
b()
print("a() returns")

a()

You'll see the order: a() -> b() -> c() -> back out again.


Local vs global scope

Variables created inside functions live in a local scope; variables created at the top level live in the global scope. Local variables are not visible outside their function.

Example (global can't see local):

def spam():
eggs = "local eggs"

spam()
# print(eggs) # NameError: eggs is not defined

Separate local scopes

Each function call has its own local scope, and different functions' locals cannot see one another's variables.

Example:

def spam():
eggs = "spam local"
bacon()
print("spam sees:", eggs)

def bacon():
eggs = "bacon local"
print("bacon sees:", eggs)

spam()

Here each eggs is separate.


Local scope can read globals

Code inside a function can read global variables (as long as you don't assign to that name locally).

Example:

eggs = "global eggs"

def spam():
print(eggs) # reads global

spam() # prints "global eggs"
print(eggs)

Same name local and global

You can have a global and a local variable with the same name, but it is confusing and usually best avoided.

Example:

eggs = "global"

def spam():
eggs = "spam local"
print(eggs)

def bacon():
eggs = "bacon local"
print(eggs)

spam() # spam local
bacon() # bacon local
print(eggs) # global

The global statement

Use global name inside a function if you want assignments to modify the global variable instead of creating a local one.

Example:

eggs = "global"

def spam():
global eggs
eggs = "spam" # modifies global

spam()
print(eggs) # spam

How to tell if a name is local or global

Rules:

  1. A variable assigned at the top level is global.
  2. Inside a function, if there's a global eggs, then eggs is global there.
  3. Otherwise, if you assign to a variable in a function, it is local.
  4. If you only read it (no assignment, no global), it refers to the global.

Example with three functions:

def spam():
global eggs # global
eggs = "spam"

def bacon():
eggs = "bacon" # local

def ham():
print(eggs) # global

eggs = "global"
spam()
print(eggs) # spam

Functions as "black boxes"

You can treat functions as black boxes: know their parameters and return values, but ignore the internal implementation. This makes it easier to use libraries and reason about code.

Example:

def add_tax(amount):
return amount * 1.1

total = add_tax(100) # you don't care how it computes, just that it returns 110

Exceptions and try/except

Errors like dividing by zero raise exceptions that normally crash the program. try/except lets you catch and handle them so the program can continue.

Naive version (crashes):

def spam(divide_by):
return 42 / divide_by

print(spam(2))
print(spam(0)) # ZeroDivisionError

Handled version:

def spam(divide_by):
try:
return 42 / divide_by
except ZeroDivisionError:
print("Error: Invalid argument.")

print(spam(2)) # 21.0
print(spam(0)) # prints error, returns None

try around calls

You can also put the function calls inside the try block; then any exception raised in them will be caught.

Example:

def spam(divide_by):
return 42 / divide_by

try:
print(spam(2))
print(spam(0))
print(spam(1)) # never reached after exception
except ZeroDivisionError:
print("Error: Invalid argument.")

Short program: Zigzag animation

The zigzag program uses functions, an infinite while True loop, time.sleep, and try/except KeyboardInterrupt to animate a row of stars moving back and forth.

import time, sys

indent = 0
indent_increasing = True

try:
while True:
print(" " * indent, end="")
print("********")
time.sleep(0.1)

if indent_increasing:
indent += 1
if indent == 20:
indent_increasing = False
else:
indent -= 1
if indent == 0:
indent_increasing = True
except KeyboardInterrupt:
sys.exit()

Short program: Spike animation

The spike program uses nested for loops, string replication, and time.sleep to draw a spike shape that grows and shrinks repeatedly.

import time, sys

try:
while True:
# Increasing part
for i in range(1, 9):
print("-" * (i * i))
time.sleep(0.1)

# Decreasing part
for i in range(7, 1, -1):
print("-" * (i * i))
time.sleep(0.1)
except KeyboardInterrupt:
sys.exit()

Overall idea of the chapter

The chapter shows how to define reusable functions, understand call stacks and variable scopes, avoid overusing globals, and use try/except to handle errors, culminating in small animated programs that combine all these ideas.