Why this matters
So far your programs have been one long sequence of statements. Real programs are built from functions: named, reusable pieces of behavior that take inputs and produce a result. Functions let you write a calculation once and use it in many places, give a name to an idea (is_valid_email, total_price), and test each piece on its own. Every later topic in this track, from collections to classes, is expressed through functions, so this is a foundation you will lean on daily.
Defining a function
def greet(name):
message = "Hello, " + name + "!"
return message
Line by line:
defstarts a function definition.greetis the function's name.(name)lists the parameters: names that will refer to the values given when the function is called.- The colon and the indented block form the body, exactly like the blocks of
ifandfor. return messageends the function and hands the value back to whoever called it.
Defining a function does not run its body. It creates a function object and binds the name greet to it. The body runs only when you call the function:
text = greet("Ada")
print(text)
print(greet("Grace"))
Hello, Ada!
Hello, Grace!
The values in the call, "Ada" and "Grace", are arguments. When the call happens, Python binds the parameter name to the argument object, runs the body, and the call expression greet("Ada") evaluates to whatever was returned.
return versus print
This distinction trips up nearly everyone at first, so take it slowly.
printwrites text to stdout for a human to read. The program cannot use that text afterward.returngives a value back to the calling code, which can store it, compute with it, or pass it on.
def square_print(n):
print(n * n)
def square_return(n):
return n * n
total = square_return(3) + square_return(4) # 9 + 16
print(total) # 25
total = square_print(3) + square_print(4)
25
9
16
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'
square_print showed 9 and 16 but gave back nothing usable, because a function that finishes without executing return returns None. A bare return (with no value) also returns None. Adding None + None is a TypeError. Rule of thumb: functions that compute should return; only the outer layer of a program, the part that talks to the user, should print.
return ends the function
return exits immediately, even from inside a loop or an if. That makes early returns a clean way to handle cases:
def sign(n):
if n > 0:
return "positive"
if n < 0:
return "negative"
return "zero"
print(sign(5), sign(-2), sign(0))
positive negative zero
No elif is needed: once a return runs, nothing below it in the function executes.
def first_multiple_of_7(start):
n = start
while True:
if n % 7 == 0:
return n
n += 1
print(first_multiple_of_7(30))
35
The while True loop would never end on its own; the return both stops the loop and leaves the function.
What happens underneath: frames and the call stack
Each time a function is called, CPython creates a frame: a small record holding the function's local names (its parameters and any names assigned inside it), the position of the instruction currently executing, and a pointer back to the caller's frame. Frames are kept on the call stack:
def inner():
return 1
def outer():
x = inner()
return x + 1
print(outer())
while inner is running, the call stack is:
+-------------------------+ <- top: running now
| inner (no locals) |
+-------------------------+
| outer x: (not yet) | paused at "x = inner()"
+-------------------------+
| <module> (the script) | paused at "print(outer())"
+-------------------------+
When inner returns 1, its frame is discarded and outer resumes exactly where it paused, now with the value 1 to bind to x. Then outer returns 2 and the module-level code resumes and prints it.
This is what a traceback shows you: the stack of frames at the moment an exception happened, oldest at the top ("most recent call last"):
Traceback (most recent call last):
File "demo.py", line 9, in <module>
print(outer())
File "demo.py", line 5, in outer
x = inner()
File "demo.py", line 2, in inner
return 1 / 0
ZeroDivisionError: division by zero
Read it bottom-up to find what went wrong, top-down to see how the program got there.
Because each call gets a fresh frame, local names in one call never collide with those of another call, even of the same function. That is what will make recursion (lesson 5) work.
Functions are objects
def is an ordinary statement that, when executed, creates a function object and binds a name to it, just as x = 5 binds x to an int object. So a function name is just a name like any other:
def shout(text):
return text.upper() + "!"
yell = shout # no parentheses: no call, just another name for the same object
print(yell("hi"))
print(type(shout))
print(shout)
HI!
<class 'function'>
<function shout at 0x7f3a2c1b8e00>
The address differs on each run. The parentheses are what make a call: shout is the function, shout("hi") is the result of calling it. You can pass functions as arguments to other functions:
def apply_twice(func, value):
return func(func(value))
print(apply_twice(shout, "hey"))
HEY!!
This idea (functions as values) comes back in the iteration module with sorting keys, and in module 13 with closures and decorators.
Order of definition
Python executes a file top to bottom. A function must be defined (its def executed) before it is called, but the body can refer to functions defined later, because names in the body are looked up only when it runs:
def main():
helper() # fine: looked up when main() runs
def helper():
print("helping")
main() # by now both defs have executed
A common layout is: imports, then function definitions, then one call such as main() at the bottom.
Common mistakes
Forgetting the parentheses. print(greet) prints <function greet at 0x...> instead of calling it.
Calling before defining.
greet("Ada")
def greet(name):
return "Hello, " + name
NameError: name 'greet' is not defined
Wrong number of arguments.
TypeError: greet() missing 1 required positional argument: 'name'
TypeError: greet() takes 1 positional argument but 2 were given
These messages name the function and say precisely what is wrong. Read them.
Printing where you meant to return, leading to None showing up later, or to TypeError: ... 'NoneType'. If you see None where you expected a value, look for a missing return.
Code after return. Lines after a return in the same block never run. Linters call this "unreachable code".
Recap
def name(params):creates a function object and binds a name to it; the body runs only when called.- Arguments are bound to parameters;
return valueexits and hands back a value; no return meansNone. - Return values for computation; print only at the edges of your program.
- Each call gets a frame on the call stack holding its locals; tracebacks are a printout of that stack.
- Functions are objects: they can be assigned to other names and passed to other functions.
Going further: in the REPL, define a function and run import dis; dis.dis(name) to see its bytecode, including RETURN_VALUE. Also try help(len) to see how built-in functions document themselves; you will write such documentation in lesson 4.