9 Creating Functions
9.1 The Wall
AI gave you 80 lines of code in one continuous block. No functions. When you asked it to change how responses work, it rewrote the entire thing. When you found a bug, you had to trace through all 80 lines to find it. You could not reuse any part of the code because nothing was separated.
You asked AI to “add functions” and it created functions with names like process() and handle() that took too many parameters. You did not know what a good function looked like, so you could not tell whether AI’s refactoring actually improved anything.
This chapter fixes that.
If you worked through Think Python, Direct AI, you already created functions to organise your project code. This chapter gives you the full picture, parameter types, return values, scope rules, and design principles that turn functions from something you use into something you understand.
9.2 Thinking Session
9.2.1 Getting Oriented
Why do we use functions in Python? I understand they group code, but what is the real benefit? Explain it from the perspective of someone who mostly reads and modifies AI-generated code. When should something be a function and when is it fine as inline code?
Your AI should explain: functions make code reusable, testable, and readable. The key insight for someone reading AI code is that functions create named abstractions. get_response(message) tells you what happens without reading the implementation. If your AI only talks about “avoiding code duplication,” push for the readability and testability angles.
9.2.2 Go Deeper
Explain Python function syntax step by step: def, parameters, default values, return values, docstrings. What is the difference between a parameter and an argument? What happens if a function does not have a return statement?
Key points: def defines a function, parameters are the names in the definition, arguments are the values passed when calling it. Default parameters use = in the definition. A function without return returns None implicitly. Docstrings go right after def as a triple-quoted string.
What is variable scope in Python? If I define a variable inside a function, can I use it outside? What about the reverse, can a function see variables defined outside it? This confuses me in AI-generated code because sometimes functions use variables I cannot find defined inside them.
9.2.3 Challenge It
What is wrong with this function, and how would you fix it?
def calculate_average(numbers):
total = 0
for n in numbers:
total += n
average = total / len(numbers)
result = calculate_average([10, 20, 30])
print(f"Average: {result}")The function calculates the average but never returns it. result will be None. The fix is adding return average at the end. AI generates this bug when it forgets the return statement. The code runs without error but produces None instead of the actual value.
9.2.4 What You Should Have Learned
defcreates a function,returnsends a value back- Parameters have names; arguments are the values passed
- Default parameters make arguments optional
- Functions without
returnreturnNone - Local variables are invisible outside their function (scope)
- Good functions do one thing and have descriptive names
9.3 The Gap
Functions are how AI organises code, and how you should evaluate whether it organised it well. A function named process_data() that does five things is poorly designed. A function named clean_user_input() that strips whitespace and lowercases text is well designed. You can now tell the difference.
In the Building Session, you will refactor your chatbot from one long block into clean, named functions.
9.4 Building Session
9.4.1 The Spec
Refactor your chatbot into functions:
display_welcome(), shows the welcome bannerget_user_name(), asks for and validates the nameget_response(user_input, message_count), returns the response stringmain(). The main loop that ties everything together
9.4.2 Prompt It
Refactor my chatbot to v0.9 using functions. Split the code into:
- display_welcome(bot_name, version), prints the welcome screen
- get_user_name(bot_name), asks for name, validates it is not empty, returns the name
- get_response(user_input, message_count), takes the input and count, returns a response string (handles questions, keywords, numbers, default)
- main(). The main loop that calls the other functions
Each function should have a one-line docstring. Call main() at the bottom with the if name == “main” guard.
Keep all existing features from v0.8.
9.4.3 Read the Code
Your AI will produce something like this:
"""PyBot v0.9: Refactored with functions."""
BOT_NAME = "PyBot"
VERSION = "0.9"
def display_welcome(bot_name, version):
"""Print the welcome banner."""
print(f"Hello! I'm {bot_name} v{version}.")
print("Type 'help' for commands.")
def get_user_name(bot_name):
"""Ask for and validate the user's name."""
name = input(f"{bot_name}: What's your name? ").strip()
while not name:
name = input("Please enter your name: ").strip()
return name
def get_response(user_input, message_count):
"""Return a response based on the input."""
if "?" in user_input:
return "Good question! I'm still learning..."
elif "python" in user_input.lower():
return "I love that topic!"
try:
number = int(user_input)
return f"{number} is {'even' if number % 2 == 0 else 'odd'}"
except ValueError:
pass
return f"You said '{user_input}'"
def main():
"""Run the chatbot."""
display_welcome(BOT_NAME, VERSION)
user_name = get_user_name(BOT_NAME)
print(f"Nice to meet you, {user_name}!")
message_count = 0
while True:
user_input = input(f"{user_name}: ").strip()
if not user_input:
continue
if user_input.lower() == "quit":
print(f"{BOT_NAME}: Goodbye, {user_name}!")
break
if user_input.lower() == "help":
print(f"{BOT_NAME}: Commands: quit, help")
continue
message_count += 1
response = get_response(user_input, message_count)
print(f"{BOT_NAME}: {response}")
if __name__ == "__main__":
main()Each function does one thing. get_user_name() returns a string. It handles its own validation loop. get_response() takes input and returns a response. It does not print anything. main() orchestrates the flow. The if __name__ == "__main__" guard means this file can be imported without running the chatbot, useful for testing.
9.4.4 Stretch It
Add a display_stats(message_count, user_name) function that is called when the user types “stats”. Also add a display_help() function that lists all available commands. Both should return None. They print directly.
9.5 Your Chatbot So Far
- Ch 1-6: Loop, structure, types, memory, formatting, validation
- Ch 7-8: Operators, built-in functions, word counting
- Ch 9: Refactored into functions: display_welcome, get_user_name, get_response, main
9.6 Quick Reference
# Define a function
def greet(name):
"""Greet the user by name."""
return f"Hello, {name}!"
# Default parameters
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# Multiple return values
def min_max(numbers):
return min(numbers), max(numbers)
lo, hi = min_max([3, 1, 7])
# No return = returns None
def log(message):
print(f"[LOG] {message}")
# Guard for importable scripts
if __name__ == "__main__":
main()