Naming a piece of work
A function is a chunk of instructions with a name, so you can run it whenever you like without writing it out again.
def word_count(text):
return len(text.split())
print(word_count("the rain in spain")) # 4
print(word_count("hello")) # 1def starts a definition. word_count is the name. text is a parameter: a name that will be attached to whatever you pass in. The indented block is the body.
Defining a function does not run it. Python reads the def, remembers it, and moves on. The body only runs when you call it, with brackets: word_count("hello").
return hands a value back
def area(width, height):
return width * height
space = area(3, 4)
print(space) # 12
print(area(2, 5) + area(1, 1)) # 11return does two things: it sends a value back to whoever called the function, and it ends the function immediately. Anything after a return that runs is never reached.
Because the call turns into a value, you can use it anywhere a value fits: store it, add it, pass it to another function.
print and return are not the same thing
This is the single biggest confusion at this stage, and it is worth being blunt about.
print puts characters on a screen for a human. return hands a value back to the rest of the program. They look the same when you are testing at the terminal, because in both cases you see 12 appear. They are completely different when the value has to be used.
def double_printed(n):
print(n * 2)
def double_returned(n):
return n * 2
a = double_printed(21) # prints 42
b = double_returned(21) # prints nothing
print(a) # None
print(b) # 42A function with no return still gives something back: None, Python's word for "no value". So a is None, and a + 1 fails with TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'. When you see NoneType in an error, the usual cause is a function that printed instead of returning.
Rule of thumb: functions should return. Print at the outer edge of your program, where you are actually talking to a person.
Default values
def greet(name, greeting="Hello"):
return f"{greeting}, {name}"
print(greet("Ivan")) # Hello, Ivan
print(greet("Ivan", "Good morning")) # Good morning, Ivan
print(greet(name="Fatima")) # Hello, FatimaA parameter with a default becomes optional. You can also pass arguments by name, which makes calls with several options readable.
Names inside stay inside
def tax_on(amount):
rate = 0.18
return amount * rate
print(tax_on(1000)) # 180.0
print(rate)NameError: name 'rate' is not definedrate lives only while tax_on is running. This is a feature. It means you can write a function without worrying about which names the rest of the program has already used.
A function you will reuse later
def build_messages(question, history=None):
messages = list(history or [])
messages.append({"role": "user", "content": question})
return messages
convo = build_messages("Explain gravity in one sentence.")
convo = build_messages("Now in Hindi.", convo)
print(len(convo)) # 2
print(convo[1]["content"]) # Now in Hindi.That is the exact structure lesson ten sends to an AI API, built by a function you wrote.
Try this now
Write average(numbers) that returns the mean of a list, and make it return 0 when the list is empty rather than crashing. Then call it and print the result from outside.
Before you move on