Chapter 2 · Python Basics
Functions
- Page 7 of 20
- 4 min read
As programs grow, you start writing the same few lines again and again. A function gives those lines a name so you can use them anywhere with one call. Functions are how you break a big problem — "build a document Q&A tool" — into small pieces you can understand and test one at a time.
Defining and calling a function
def count_words(text):
"""Return the number of words in text."""
return len(text.split())
print(count_words("Python makes AI work easier"))
print(count_words("Hello"))5
1defstarts the definition, followed by the name and the parameters in brackets.- The indented block is the body. It runs only when the function is called.
returnsends a value back to whoever called the function.- The text in triple quotes right under
defis a docstring: documentation that editors show when you hover over the function. Write one for every function you share.
Parameters, defaults and keyword arguments
Parameters can have default values, used when the caller does not give one. Callers can pass arguments by position or by name (keyword arguments). Named arguments make calls self-explaining — every AI SDK uses them heavily:
def build_prompt(task, text, language="English", max_words=50):
return f"{task} the text below. Answer in {language}, in at most {max_words} words.\n{text}"
print(build_prompt("Summarise", "Python is a programming language..."))
print()
print(build_prompt("Translate", "Good morning", language="Bangla", max_words=10))Summarise the text below. Answer in English, in at most 50 words.
Python is a programming language...
Translate the text below. Answer in Bangla, in at most 10 words.
Good morning(\n inside a string means "new line".)
Returning several values
A function can return several values at once — Python packs them into a tuple, and you unpack them on the other side:
def stats(numbers):
total = sum(numbers)
return min(numbers), max(numbers), total / len(numbers)
low, high, mean = stats([4, 8, 15, 16, 23, 42])
print(low, high, round(mean, 2))4 42 18.0print is not return
A very common beginner mix-up. print shows something on screen; return hands a value back to the code. A function with no return gives back None, Python's "nothing" value:
def greet(name):
print("Hi", name) # prints, but returns nothing
result = greet("Mina")
print(result)Hi Mina
NoneIf you want to use a function's result later — store it, compare it, send it to an API — the function must return it.
Scope: where a variable lives
Variables created inside a function are local: they exist only while the function runs. A function can read variables from outside (global ones), but the outside cannot see its locals:
limit = 100 # a global variable
def check(tokens):
remaining = limit - tokens # remaining exists only inside the function
return remaining
print(check(30))
print(remaining)70
Traceback (most recent call last):
File "main.py", line 8, in <module>
print(remaining)
^^^^^^^^^
NameError: name 'remaining' is not definedGood functions get everything they need through parameters and give results back through return, instead of reading and changing globals. They are easier to test and reuse.
*args and **kwargs
A parameter written *name collects any extra positional arguments into a tuple; **name collects extra keyword arguments into a dict. You will see this pattern in library code:
def log(level, *messages, **details):
print(level, "|", " ".join(messages), "|", details)
log("INFO", "Request", "sent")
log("ERROR", "Timeout", model="gpt-5.5", seconds=30)INFO | Request sent | {}
ERROR | Timeout | {'model': 'gpt-5.5', 'seconds': 30}lambda: tiny unnamed functions
lambda arguments: expression makes a small function in one line. Its most common use is as the key that tells sorted(), max() or min() what to compare — here, sorting search results by their score:
results = [("doc-a", 0.62), ("doc-b", 0.91), ("doc-c", 0.75)]
best_first = sorted(results, key=lambda pair: pair[1], reverse=True)
print(best_first)
names = sorted(["Mina", "abir", "Zara"], key=str.lower)
print(names)[('doc-b', 0.91), ('doc-c', 0.75), ('doc-a', 0.62)]
['abir', 'Mina', 'Zara']A classic bug: a mutable default
Default values are created once, when the function is defined — not on every call. A list as a default is therefore shared between calls:
def add_tag(tag, tags=[]): # a bug waiting to happen
tags.append(tag)
return tags
print(add_tag("ai"))
print(add_tag("python")) # surprise: "ai" is still there
def add_tag_fixed(tag, tags=None):
if tags is None:
tags = []
tags.append(tag)
return tags
print(add_tag_fixed("ai"))
print(add_tag_fixed("python"))['ai']
['ai', 'python']
['ai']
['python']The rule: never use a list or dict as a default value. Use None and create the list inside the function.
Try it yourself
- Write
estimate_tokens(text)that returns roughlylen(text) / 4, rounded up (OpenAI's rule of thumb for English is about 4 characters per token). - Write
make_message(role, content)that returns a message dict, and use it to build a conversation list. - Given a list of (name, score) tuples, use
max()with a lambda to find the best one.