Functions
As your programs grow, you will write the same logic in more than one place. Functions let you write logic once, name it, and use it everywhere. Fix it in one place and every call gets the fix automatically.
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # "Hello, Alice!"
print(greet("Bob")) # "Hello, Bob!"Write it once, use it everywhere, fix it in one place.
Defining a function
The def keyword starts a function definition, followed by the name, parentheses, a colon, and an indented body. A function does nothing until you call it. Define it with def, then call it by name with ().
def say_hello():
print("Hello!")
say_hello() # call the functionParameters and arguments
Parameters are the inputs your function expects. List them inside the parentheses. When you call the function, the values you pass are matched to the parameters in order.
def greet(name, greeting):
print(f"{greeting}, {name}!")
greet("Alice", "Hello") # "Hello, Alice!"
greet("Bob", "Hi") # "Hi, Bob!"Parameters vs arguments
Parameter is the name in the function definition. Argument is the actual value you pass when you call the function. In practice, people use the words interchangeably. Just be aware of the distinction when reading docs.
Default values
You can give parameters a default value. If the caller does not provide that argument, the default is used. Parameters with defaults must come after parameters without defaults.
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice") # "Hello, Alice!"
greet("Alice", "Hi") # "Hi, Alice!"Parameters with defaults must come after parameters without defaults.
Keyword arguments
When calling a function, you can name the arguments. This makes calls readable, especially for functions with many parameters, and lets you pass them in any order.
def describe_player(name, score, level):
print(f"{name} | Score: {score} | Level: {level}")
describe_player("Alice", 87, 5) # positional
describe_player(name="Alice", level=5, score=87) # keyword, any order
describe_player("Alice", level=5, score=87) # mix: positional firstReturn values
return sends a value back to the caller. Without return, a function gives back None. Once return runs, the function exits immediately. Any code after it in that block is skipped.
def add(a, b):
return a + b
result = add(3, 4) # result = 7
print(result)return also exits the function immediately. Any code after it in that block does not run.
Returning multiple values
Python lets you return multiple values by separating them with commas. The caller receives them as a tuple and can unpack them into separate names in one line.
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([3, 7, 1, 9, 4])
print(low, high) # 1 9The low, high = ... syntax is unpacking: Python assigns each returned value to the corresponding name.
Scope
Variables created inside a function exist only inside that function. You cannot see them from outside. Variables defined outside all functions are visible everywhere, but you cannot change them from inside a function without an explicit declaration.
def calculate():
result = 42 # local to this function
return result
calculate()
print(result) # NameError, result doesn't exist out herecount = 0
def increment():
global count # declare you want to modify the global
count += 1
increment()
print(count) # 1Using global should be a last resort. It makes code harder to reason about. Prefer passing values in and returning them out.
*args and **kwargs
Sometimes you do not know how many arguments a function will receive. *args collects any number of positional arguments into a tuple. **kwargs collects any number of keyword arguments into a dictionary. The names args and kwargs are conventions; the stars are what matter.
def total(*args):
return sum(args)
total(1, 2, 3) # 6
total(1, 2, 3, 4, 5) # 15def display(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
display(name="Alice", score=87, level=5)You can mix them with regular parameters. Regular parameters come first:
def describe(title, *tags, **metadata):
print(f"{title} | tags: {tags} | meta: {metadata}")
describe("Python intro", "beginner", "python", author="Alice", year=2024)Docstrings
A docstring is a string at the top of a function that describes what it does. Python editors and tools use it to show help when you hover over a function call. Use triple quotes, and write one line for simple functions.
def normalise(value, min_val, max_val):
"""Scale a value to the 0-1 range given the known min and max."""
return (value - min_val) / (max_val - min_val)def build_url(base, version, resource, *, secure=True):
"""
Build an API endpoint URL.
Returns a fully-qualified URL string. If secure is False,
the URL will use http instead of https.
"""
scheme = "https" if secure else "http"
base = base.replace("https://", "").replace("http://", "")
return f"{scheme}://{base}/{version}/{resource}"Write a docstring for any function that is not obviously self-explanatory from its name and signature.
Type hints
Type hints let you annotate what types a function expects and returns. Python does not enforce them at runtime, but editors use them to catch mistakes before you run anything. The -> before the colon specifies the return type.
def greet(name: str, score: int) -> str:
return f"{name} scored {score}"def log(message: str) -> None:
print(f"[LOG] {message}")def top_scores(scores: list[int], n: int) -> list[int]:
return sorted(scores, reverse=True)[:n]Type hints are optional but valuable on any function that will be called from multiple places. They are documentation that tools can verify.
Functions as values
Functions in Python are values, just like strings or numbers. You can assign them to variables and pass them to other functions. This is how sorted() accepts a key= function.
def double(x):
return x * 2
def apply(func, value):
return func(value)
apply(double, 5) # 10Passing functions as arguments shows up constantly with sorted(), map(), and filter(). You will also see it in the Lambda, comprehensions, and zip chapter.
In practice
Two functions that work together: letter_grade converts a score to a letter, and summarise calls it for every score in a list:
def letter_grade(score: int) -> str:
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
else:
return "F"
def summarise(scores: list[int]) -> None:
total = sum(scores)
avg = total / len(scores)
grades = [letter_grade(s) for s in scores]
print(f"Average: {avg:.1f}")
print(f"Grades: {', '.join(grades)}")
summarise([87, 92, 74, 65, 91])
