what is a function?
A function is code that is callable, this means I can write code to do something one time, and call the name for it to do that thing at a different time from when I write it.
functions can make code simpler, easier to read, test, reuse, maintain and improve - all the good things.
how to make a function
functions are made with
the def keyword
a name
parentheses and a colon at the end
the code that makes up the function (its body) comes after the colon
def name_of_function():
the body of the function
...
preview
I have these tests by the end of the chapter
1def test_making_a_function_w_pass():
2 def w_pass():
3 pass
4
5 assert w_pass() is None
6
7
8def test_making_a_function_w_return():
9 def w_return():
10 return
11
12 assert w_return() is None
13
14
15def test_making_a_function_w_return_none():
16 def w_return_none():
17 return None
18
19 assert w_return_none() is None
20
21
22def test_what_happens_after_functions_return():
23 def return_leaves_the_function():
24 return None
25 return 'only one way for this line to run'
26
27 assert return_leaves_the_function() is None
28
29
30def test_constant_function():
31 def constant():
32 return 'the same thing'
33
34 assert constant() == 'the same thing'
35
36
37# Exceptions seen
38# AssertionError
39# NameError
40# TypeError
questions about functions
Questions to think about as I go through the chapter
start the project
I name this project
functionsI open a terminal
I use uv to make a directory for the project and initialize it
uv init functionsthe terminal shows
Initialized project `functions` at `.../pumping_python/functions`then goes back to the command line.
I change directory to the project
cd functionsthe terminal shows I am in the
functionsfolder.../pumping_python/functionsI make a directory for the tests
mkdir teststhe terminal goes back to the command line.
I make the
testsdirectory a Python packageDanger
use 2 underscores (__) before and after
initfor__init__.pynot_init_.pytouch tests/__init__.pyNew-Item tests/__init__.pythe terminal goes back to the command line.
I use the mv program to change the name of
main.pytotest_functions.pyand move it to thetestsfoldermv main.py tests/test_functions.pyMove-Item main.py tests/test_functions.pythe terminal goes back to the command line.
I open
test_functions.pyI delete all the text then add the first failing test to
test_functions.py1def test_failure(): 2 assert False is TrueI go back to the terminal to make a requirements file for the Python packages I need
echo "pytest" > requirements.txtthe terminal goes back to the command line.
I add pytest-watcher to the requirements file
echo "pytest-watcher" >> requirements.txtthe terminal goes back to the command line.
I use uv to install pytest-watcher with the requirements file
uv add --requirement requirements.txtthe terminal shows that it installed pytest-watcher and its dependencies.
I add the new files and folders to git for tracking
git add .the terminal goes back to the command line.
I add a git commit message
git commit --all --message 'setup project'the terminal shows
[main (root-commit) a0b12c3] setup project 8 files changed, X insertions(+) create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 tests/__init__.py create mode 100644 tests/test_functions.py create mode 100644 uv.lockthen goes back to the command line.
I use pytest-watcher to run the tests automatically
uv run pytest-watcher . --nowthe terminal is my friend, and shows AssertionError
======================== FAILURES ======================== ______________________ test_failure ______________________ def test_failure(): > assert False is True E assert False is True test_functions.py:2: AssertionError ================ short test summary info ================= FAILED test_functions.py::test_failure - assert False is True =================== 1 failed in X.YZs ====================if the terminal does not show the same error, then check if
your
tests/__init__.pyhas two underscores (__) before and afterinitfor__init__.pynot_init_.pyyou ran
echo "pytest-watcher" >> requirements.txt, to addpytest-watcherto the requirements file
and try
uv run pytest-watcher . --nowagainI add AssertionError to the list of Exceptions seen, in
test_functions.py1def test_failure(): 2 assert False is True 3 4 5# Exceptions seen 6# AssertionErrorI change True to False in the assertion
1def test_failure(): 2 # assert False is True 3 assert False is False 4 5 6# Exceptions seen 7# AssertionErrorthe test passes.
test_making_a_function_w_pass
The simplest function I can make is with the pass keyword.
RED: make it fail
I change
test_failureto test_making_a_function_w_pass1def test_making_a_function_w_pass(): 2 w_pass 3 4 5# Exceptions seen 6# AssertionErrorthe terminal is my friend, and shows NameError
> w_pass E NameError: name 'w_pass' is not definedbecause Python does not know what I mean by
w_passsince I do not have a definition for it intest_functions.py.I add NameError to the list of Exceptions seen
5# Exceptions seen 6# AssertionError 7# NameError
what is a variable?
I can define a name in Python with a variable. A variable is a name that is used to point to an object. For example, in Mathematics we use x to represent any number.
Every time I use the name, Python “knows” that I am referring to the object pointed the name to.
GREEN: make it pass
I change w_pass to a variable by pointing it to None (the simplest object)
1def test_making_a_function_w_pass():
2 # w_pass
3 w_pass = None
4
5
6# Exceptions seen
7# AssertionError
8# NameError
the test passes.
REFACTOR: make it better
I add an assertion
1def test_making_a_function_w_pass(): 2 # w_pass 3 w_pass = None 4 assert w_pass is 0 5 6 7# Exceptions seenthe terminal is my friend, and shows AssertionError
E assert None is 0because
w_passpoints to None. Using substitutionw_pass = None assert w_pass is 0 assert None is 0which raises AssertionError since None is not the same object as an integer (whole number without decimals).
I change the assertion to make the statement True
1def test_making_a_function_w_pass(): 2 # w_pass 3 w_pass = None 4 # assert w_pass is 0 5 assert w_pass is None 6 7 8# Exceptions seenthe test passes.
how to call a function
I can call a function with by placing parentheses (()) after the name when I use it after it is defined.
name_of_function()
A call to a function gets the output of the function
name_of_function() -> output_object
RED: make it fail
I add parentheses to the assertion to call
w_passinside test_making_a_function_w_pass1def test_making_a_function_w_pass(): 2 # w_pass 3 w_pass = None 4 # assert w_pass is 0 5 # assert w_pass is None 6 assert w_pass() is None 7 8 9# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause the assertion calls
w_passwhich points to None and I cannot call None like a function. Using substitutionw_pass = None # point the name to the object w_pass() # call the name None() # substitute the value for the nameNone()raises TypeError.I add TypeError to the list of Exceptions seen
9# Exceptions seen 10# AssertionError 11# NameError 12# TypeError
GREEN: make it pass
I use the def and pass keywords to make
w_passthe simplest function I can make1def test_making_a_function_w_pass(): 2 # w_pass 3 # w_pass = None 4 # assert w_pass is 0 5 # assert w_pass is None 6 def w_pass(): 7 pass 8 9 assert w_pass() is None 10 11 12# Exceptions seenThe test passes because I get None when I call
w_passw_pass() -> NoneThe assertion -
assert w_pass() is Nonechecks if the result of a call tow_pass, is the same object as None.The function definition simply says pass and the test passes.
pass is a keyword that allows the function definition to follow Python language rules (the function must have a body).
The test passes because all functions return None by default, as if they have an invisible line that says return None, which leads me to the next test, but first cleanup time.
REFACTOR: make it better
I remove the commented lines
1def test_making_a_function_w_pass(): 2 def w_pass(): 3 pass 4 5 assert w_pass() is None 6 7 8# Exceptions seen 9# AssertionError 10# NameError 11# TypeErrorI open another terminal then change directory to
functionscd functionsI add a git commit message
git commit --all --message \ 'add test_making_a_function_w_pass'the terminal shows a summary of the changes then goes back to the command line.
test_making_a_function_w_return
I can also make a function with a return statement.
RED: make it fail
I go back to the terminal where the tests are running
I add a new test with a name
1def test_making_a_function_w_pass(): 2 def w_pass(): 3 pass 4 5 assert w_pass() is None 6 7 8def test_making_a_function_w_return(): 9 w_return 10 11 12# Exceptions seenthe terminal is my friend, and shows NameError
> w_return E NameError: name 'w_return' is not definedbecause Python does not know what I mean by
w_returnsince I do not have a definition for it intest_functions.py.
GREEN: make it pass
I change w_return to a variable by pointing it to None (the simplest object)
8def test_making_a_function_w_return():
9 # w_return
10 w_return = None
11
12
13# Exceptions seen
the test passes.
REFACTOR: make it better
I add an assertion for
w_return8def test_making_a_function_w_return(): 9 # w_return 10 w_return = None 11 assert w_return is 1 12 13 14# Exceptions seenthe terminal is my friend, and shows AssertionError
E assert None is 1because
w_returnpoints to None. Using substitutionw_return = None assert w_return is 1 assert None is 1which raises AssertionError since None is not the same object as an integer (whole number without decimals).
I change the assertion to make the statement True
8def test_making_a_function_w_return(): 9 # w_return 10 w_return = None 11 # assert w_return is 1 12 assert w_return is None 13 14 15# Exceptions seenthe test passes.
I add parentheses to call
w_returninside test_making_a_function_w_return8def test_making_a_function_w_return(): 9 # w_return 10 w_return = None 11 # assert w_return is 1 12 # assert w_return is None 13 assert w_return() is None 14 15 16# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause the assertion calls
w_returnwhich points to None and I cannot call None like a function. Using substitutionw_return = None # point the name to the object w_return() # call the name None() # substitute the value for the nameNone()raises TypeError.I use the def and pass keywords to change
w_returnto the simplest function I can make8def test_making_a_function_w_return(): 9 # w_return 10 # w_return = None 11 # assert w_return is 1 12 # assert w_return is None 13 def w_return(): 14 pass 15 16 assert w_return() is None 17 18 19# Exceptions seenthe test passes.
the return statement
The return keyword is used to define what a function gives as output when it is called.
I change pass to a return statement with the return keyword
8def test_making_a_function_w_return(): 9 # w_return 10 # w_return = None 11 # assert w_return is 1 12 # assert w_return is None 13 def w_return(): 14 # pass 15 return 16 17 assert w_return() is None 18 19 20# Exceptions seenThe test is still green because I get None when I call<how to call a function>```w_return`
w_return() -> NoneThe assertion -
assert w_return() is Nonechecks if the result of a call tow_return, is the same object as None.The function definition simply says return and the test passes.
return is a keyword that defines what the function gives as output.
I remove the commented lines
1def test_making_a_function_w_pass(): 2 def w_pass(): 3 pass 4 5 assert w_pass() is None 6 7 8def test_making_a_function_w_return(): 9 def w_return(): 10 return 11 12 assert w_return() is None 13 14 15# Exceptions seenI add a git commit message in the other terminal
git commit --all --message \ 'add test_making_a_function_w_return'the terminal shows a summary of the changes then goes back to the command line.
I can make a function with a return statement.
I have two functions with different statements, and the tests show that they both return None
def w_pass():
pass
def w_return():
return
their contents are different, their results are the same because “all functions return None by default, as if they have an invisible line that says return None”, which leads me to the next test.
test_making_a_function_w_return_none
I can make a function with a return statement that says exactly what the function returns.
RED: make it fail
I go back to the terminal where the tests are running
I add a test with a name
8def test_making_a_function_w_return(): 9 def w_return(): 10 return 11 12 assert w_return() is None 13 14 15def test_making_a_function_w_return_none(): 16 w_return_none 17 18 19# Exceptions seenthe terminal is my friend, and shows NameError
NameError: name 'w_return_none' is not definedbecause Python does not know what I mean by
w_return_nonesince I do not have a definition for it in this file.
GREEN: make it pass
I change w_return_none to a variable by pointing it to None (the simplest object)
15def test_making_a_function_w_return_none():
16 # w_return_none
17 w_return_none = None
18
19
20# Exceptions seen
the test passes.
REFACTOR: make it better
I add an assertion for
w_return_none15def test_making_a_function_w_return_none(): 16 # w_return_none 17 w_return_none = None 18 assert w_return_none is 2 19 20 21# Exceptions seenthe terminal is my friend, and shows AssertionError
E assert None is 2because
w_return_nonepoints to None. Using substitutionw_return_none = None assert w_return_none is 2 assert None is 2which raises AssertionError since None is not the same object as an integer (a whole number without decimals).
I change the assertion to make the statement True
15def test_making_a_function_w_return_none(): 16 # w_return_none 17 w_return_none = None 18 # assert w_return_none is 2 19 assert w_return_none is None 20 21 22# Exceptions seenthe test passes.
I add parentheses to call
w_return_noneinside test_making_a_function_w_return_none15def test_making_a_function_w_return_none(): 16 # w_return_none 17 w_return_none = None 18 # assert w_return_none is 2 19 # assert w_return_none is None 20 assert w_return_none() is None 21 22 23# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause the assertion calls
w_return_nonewhich points to None and I cannot call None like a function. Using substitutionw_return_none = None # point the name to the object w_return_none() # call the name None() # substitute the value for the nameNone()raises TypeError.I change
w_return_noneto a function with the def and return keywords15def test_making_a_function_w_return_none(): 16 # w_return_none 17 # w_return_none = None 18 # assert w_return_none is 2 19 # assert w_return_none is None 20 def w_return_none(): 21 return 22 23 assert w_return_none() is None 24 25 26# Exceptions seenthe test passes.
I add None to the return statement of
w_return_none15def test_making_a_function_w_return_none(): 16 # w_return_none 17 # w_return_none = None 18 # assert w_return_none is 2 19 # assert w_return_none is None 20 def w_return_none(): 21 # return 22 return None 23 24 assert w_return_none() is None 25 26 27# Exceptions seenthe test is still green.
I change None to
'something'15def test_making_a_function_w_return_none(): 16 # w_return_none 17 # w_return_none = None 18 # assert w_return_none is 2 19 # assert w_return_none is None 20 def w_return_none(): 21 # return 22 # return None 23 return 'something' 24 25 assert w_return_none() is None 26 27 28# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: assert 'something' is Nonebecause I get
'something'when I callw_return_nonew_return_none() -> NoneUsing substitution
assert w_return_none() is None assert 'something' is Nonewhich raises AssertionError because a string (anything in quotes) is not None.
I undo the change
18def test_making_a_function_w_return_none(): 19 # w_return_none 20 # w_return_none = None 21 # assert w_return_none is 2 22 # assert w_return_none is None 23 def w_return_none(): 24 # return 25 return None 26 # return 'something' 27 28 assert w_return_none() is None 29 30 31# Exceptions seenI remove the commented lines
8def test_making_a_function_w_return(): 9 def w_return(): 10 return 11 12 assert w_return() is None 13 14 15def test_making_a_function_w_return_none(): 16 def w_return_none(): 17 return None 18 19 assert w_return_none() is None 20 21 22# Exceptions seenI add a git commit message in the other terminal
git commit --all --message \ 'add test_making_a_function_w_return_none'the terminal shows a summary of the changes then goes back to the command line.
I can make a function with return None..
I have three functions with different statements, and the tests show that they all return None
def w_pass():
pass
def w_return():
return
def w_return_none():
return None
their contents are different, their results are the same because “all functions return None by default, as if they have an invisible line that says …”
I like to write my functions with explicit return statements, so that anyone can see what the function returns without having to think about it.
test_what_happens_after_functions_return
The return statement is the last thing to run in a function.
RED: make it fail
I go back to the terminal where the tests are running
I add a test to
test_functions.py15def test_making_a_function_w_return_none(): 16 def w_return_none(): 17 return None 18 19 assert w_return_none() is None 20 21 22def test_what_happens_after_functions_return(): 23 return_leaves_the_function 24 25 26# Exceptions seenthe terminal is my friend, and shows NameError
NameError: name 'return_leaves_the_function' is not definedbecause Python does not know what I mean by
return_leaves_the_functionsince I do not have a definition for it in this file, yet.
GREEN: make it pass
I change return_leaves_the_function to a variable by pointing it to None (the simplest object)
22def test_what_happens_after_functions_return():
23 # return_leaves_the_function
24 return_leaves_the_function = None
25
26
27# Exceptions seen
the test passes.
REFACTOR: make it better
I add an assertion
22def test_what_happens_after_functions_return(): 23 # return_leaves_the_function 24 return_leaves_the_function = None 25 assert return_leaves_the_function is 'something' 26 27 28# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: assert None is 'something'because
return_leaves_the_functionpoints to None. Using substitutionreturn_leaves_the_function = None assert return_leaves_the_function is 'something' assert None is 'something'which raises AssertionError since None is not the same object as a string.
I change the assertion to make the statement True
22def test_what_happens_after_functions_return(): 23 # return_leaves_the_function 24 return_leaves_the_function = None 25 # assert return_leaves_the_function is 'something' 26 assert return_leaves_the_function is None 27 28 29# Exceptions seenthe test passes.
I add parentheses to call
return_leaves_the_functioninside test_making_a_function_w_return_none22def test_what_happens_after_functions_return(): 23 # return_leaves_the_function 24 return_leaves_the_function = None 25 # assert return_leaves_the_function is 'something' 26 # assert return_leaves_the_function is None 27 assert return_leaves_the_function() is None 28 29 30# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause the assertion calls
return_leaves_the_functionwhich points to None and I cannot call None like a function. Using substitutionreturn_leaves_the_function = None # point the name to the object return_leaves_the_function() # call the name None() # substitute the value for the nameNone()raises TypeError.I change
return_leaves_the_functionto a function with the def and return keywords22def test_what_happens_after_functions_return(): 23 # return_leaves_the_function 24 # return_leaves_the_function = None 25 # assert return_leaves_the_function is 'something' 26 # assert return_leaves_the_function is None 27 def return_leaves_the_function(): 28 return None 29 30 assert return_leaves_the_function() is None 31 32 33# Exceptions seenthe test passes.
I add a return statement
22def test_what_happens_after_functions_return(): 23 # return_leaves_the_function 24 # return_leaves_the_function = None 25 # assert return_leaves_the_function is 'something' 26 # assert return_leaves_the_function is None 27 def return_leaves_the_function(): 28 return 'something' 29 return None 30 31 assert return_leaves_the_function() is None 32 33 34# Exceptions seenthe terminal is my friend, and shows AssertionError
E AssertionError: assert 'something' is Nonebecause I get
'something'when I callreturn_leaves_the_functionw_return_leaves_the_function() -> 'something'Using substitution
assert return_leaves_the_function() is None assert 'something' is Nonewhich raises AssertionError because a string is not None.
The return statement is the last thing to run in a function, it exits after the return statement.
def return_leaves_the_function(): └── return 'something' return NoneIt never gets to
return Nonebecause it leaves afterreturn 'something'.The second return statement will never run. It is not reachable (this is called dead code).
This is why I can treat a call to a function as the object it returns.
Tip
The Integrated Development Environment (IDE) shows that the second return statement will not run by graying it out.
I move
return None, to make it the first return statement22def test_what_happens_after_functions_return(): 23 # return_leaves_the_function 24 # return_leaves_the_function = None 25 # assert return_leaves_the_function is 'something' 26 # assert return_leaves_the_function is None 27 def return_leaves_the_function(): 28 return None 29 return 'something' 30 31 assert return_leaves_the_function() is None 32 33 34# Exceptions seenthe test is green again.
I change the second return statement as a reminder
22def test_what_happens_after_functions_return(): 23 # return_leaves_the_function 24 # return_leaves_the_function = None 25 # assert return_leaves_the_function is 'something' 26 # assert return_leaves_the_function is None 27 def return_leaves_the_function(): 28 return None 29 # return 'something' 30 return 'only one way for this line to run' 31 32 assert return_leaves_the_function() is None 33 34 35# Exceptions seenthe second return statement is now like a comment, and the test is still green because the return statement is the last thing to run in a function.
I remove the commented lines
15def test_making_a_function_w_return_none(): 16 def w_return_none(): 17 return None 18 19 assert w_return_none() is None 20 21 22def test_what_happens_after_functions_return(): 23 def return_leaves_the_function(): 24 return None 25 return 'only one way for this line to run' 26 27 assert return_leaves_the_function() is None 28 29 30# Exceptions seenI add a git commit message in the other terminal
git commit --all --message \ 'add test_what_happens_after_functions_return'the terminal shows a summary of the changes then goes back to the command line.
test_constant_function
There are functions that always return the same thing when they are called. They are singletons or constant functions.
RED: make it fail
I go back to the terminal where the tests are running
I add a test
22def test_what_happens_after_functions_return(): 23 def return_leaves_the_function(): 24 return None 25 return 'only one way for this line to run' 26 27 assert return_leaves_the_function() is None 28 29 30def test_constant_function(): 31 constant 32 33 34# Exceptions seenthe terminal is my friend, and shows NameError
NameError: name 'constant' is not definedbecause Python does not know what I mean by
constantsince I do not have a definition for it in this file.
GREEN: make it pass
I change constant to a variable by pointing it to None
30def test_constant_function():
31 # constant
32 constant = None
33
34
35# Exceptions seen
the test passes.
REFACTOR: make it better
I add an assertion
30def test_constant_function(): 31 # constant 32 constant = None 33 assert constant is 'the same thing' 34 35 36# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: assert None is 'the same thing'because
constantpoints to None. Using substitution since I can treat a call to a function as the object it returnsconstant = None assert constant is 'the same thing' assert None is 'the same thing'which raises AssertionError since None is not the same object as a string(anything in quotes).
I change the assertion to make the statement True
30def test_constant_function(): 31 # constant 32 constant = None 33 # assert constant is 'the same thing' 34 assert constant is None 35 36 37# Exceptions seenthe test passes.
I add parentheses to call
constant30def test_constant_function(): 31 # constant 32 constant = None 33 # assert constant is 'the same thing' 34 # assert constant is None 35 assert constant() is None 36 37 38# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause the assertion calls
constantwhich points to None and I cannot call None like a function. Using substitution since I can treat a call to a function as the object it returnsconstant = None # point the name to the object constant() # call the name None() # substitute the value for the nameNone()raises TypeError.I change
constantto a function with the def and return keywords30def test_constant_function(): 31 # constant 32 # constant = None 33 # assert constant is 'the same thing' 34 # assert constant is None 35 def constant(): 36 return None 37 38 assert constant() is None 39 40 41# Exceptions seenthe test passes.
I change the return statement
30def test_constant_function(): 31 # constant 32 # constant = None 33 # assert constant is 'the same thing' 34 # assert constant is None 35 def constant(): 36 # return None 37 return 'the same thing' 38 39 assert constant() is None 40 41 42# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: assert 'the same thing' is NoneI change the assertion to make it True
30def test_constant_function(): 31 # constant 32 # constant = None 33 # assert constant is 'the same thing' 34 # assert constant is None 35 def constant(): 36 # return None 37 return 'the same thing' 38 39 # assert constant() is None 40 assert constant() == 'the same thing' 41 42 43# Exceptions seenthe test passes because I get
'the same thing'when I callconstantconstant() -> 'the same thing'I remove the commented lines
22def test_what_happens_after_functions_return(): 23 def return_leaves_the_function(): 24 return None 25 return 'only one way for this line to run' 26 27 assert return_leaves_the_function() is None 28 29 30def test_constant_function(): 31 def constant(): 32 return 'the same thing' 33 34 assert constant() == 'the same thing' 35 36 37# Exceptions seen 38# AssertionError 39# NameError 40# TypeErrorI add a git commit message in the other terminal
git commit --all --message 'add test_constant_function'the terminal shows a summary of the changes then goes back to the command line.
A constant function always returns the same thing. when called, I can use them in place of variables. The number of cases where they are faster than variables is pretty small. It is something like if the function is called less than 10 times (who’s counting?)
All the functions I have written so far return the same value every time they are called.
close the project
I close
test_functions.pyandfunctions.pyI click in the terminal where the tests are running
I use q on the keyboard to leave the tests. The terminal goes back to the command line.
I change directory to the parent of
functionscd ..the terminal shows
.../pumping_pythonI am back in the
pumping_pythondirectory.
review
I ran tests to show that I can make functions with the def, pass and return keywords.
code from the chapter
what is next?
rate pumping python
If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too.