PyDA Course
📖 Lesson 11 Beginner ⏱ 18 min ⚡ +10 XP ColabKagglenbviewerBinderDeepnoteGitHub def · parameters · return · docstrings

Defining Functions

Create reusable code blocks with def, parameters, and return values.

🎯 What you'll learn:
  • Define and call functions with def
  • Use positional, keyword, and default parameters
  • Return values from functions
  • Write docstrings for function documentation

From formula to named machine

Mathematics abhors repetition. You learned f(x)=x25x+6f(x) = x^2 - 5x + 6 as a rule, one definition, used a thousand times, on a thousand different inputs:

f(x)=x25x+6,f(2)=0.f(x) = x^2 - 5x + 6, \qquad f(2) = 0.

Python’s def is the same move: bind a name to a computation, so that any caller can apply it. The function is a machine with labeled input slots and one output door:

python
def add(a, b):
    return a + b

result = add(3, 5)  # 8

The name, the parentheses holding the parameters a,ba, b, the colon starting the recipe, this is the definition. The call add(3, 5) is applying the rule at a=3a=3, b=5b=5, exactly as f(2)f(2) applies a rule at x=2x=2.

Defining and calling

The first function you write changes the world a greeting at a time:

python
def greet(name):
    """Print a greeting for the given name."""
    print(f"Hello, {name}!")

greet("Alice")  # Hello, Alice!

Three parts deserve names. The parameters are the variables in the definition, the input slots xx. The arguments are the concrete values supplied at the call site, the input 22. And the triple-quoted line inside is the docstring: documentation living next to the code, so help(greet) can answer what the function does.

Return: the output door

print sends text to the screen; return hands a value back to the caller. The distinction is subtle and decisive:

python
def add(a, b):
    return a + b

result = add(3, 5)          # result == 8
printed = print("8")        # printed is None — print returns nothing

A function without return quietly returns None, the machine produces no output. When you want the arithmetic result of your function to flow onward, remember: return, not print.

Default parameters

Some parameters have a natural setting most calls will keep. Give them a default, and callers may override:

python
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

print(greet("Alice"))              # Hello, Alice!
print(greet("Bob", "Hey"))         # Hey, Bob!

The rule for ordering is rigid: default parameters come after non-default ones. def f(x, y=5) is legal; def f(x=1, y) is a syntax error, because Python resolves arguments by position from the left, and a gap would be ambiguous.

Keyword arguments

Arguments can also arrive named, which buys clarity when the parameter set grows:

python
def create_user(name, age, role="student"):
    return {"name": name, "age": age, "role": role}

user = create_user(age=25, name="Alice", role="admin")

Named arguments may be given in any order, the parameter name is the label on each package. A call that names its inputs reads like a sentence instead of a code in need of decoding.

*args and **kwargs

What if the number of inputs is unknown in advance? A sum doesn’t know how many addends it will receive. *args collects any number of positional arguments into one tuple; **kwargs collects named arguments into one dict:

python
def total(*args):
    return sum(args)

print(total(1, 2, 3, 4))  # 10

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25)

The star is the gesture: * unfolds the argument list into a bundle. This is the difference between a sum with a fixed signature and a sum that accepts i=1nai\sum_{i=1}^{n} a_i for any nn.

Early return as a guard

Some code starts by checking for the one case that must not proceed. Reasons of the form “unless you are b=0b=0 are stated as a guard at the top, returning immediately:

python
def divide(a, b):
    if b == 0:
        return None
    return a / b

A guard clause collapses an if/else pair into a straight line: the failure case exits early, and the honest path runs un-nested.

A worked example: the machine f

The quadratic that opened the lesson becomes three return statements:

python
def quad(x):
    """Return x² − 5x + 6."""
    return x * x - 5 * x + 6

quad(2)    # 0
quad(3)    # 0
quad(1)    # 2

The same rule, three inputs. The formula f(x)=x25x+6f(x) = x^2 - 5x + 6 turns into a reusable machine: define once, apply a thousand times, and the docstring records which rule it encloses.

Common pitfalls

  • Mutable default arguments. def f(items=[]) creates one list shared across every call, items pile up between calls. Default to None and build the list inside.
  • Forgetting return. A function without it returns None; you asked for a value and got a shadow.
  • Too many parameters. Past three or four, the slots turn into a puzzle. Group related arguments in a dict or dataclass.
  • Calling a function defined later. Python executes top to bottom; calling f() before def f reaches the interpreter raises a NameError. Define before you call.

🧩 Challenges

🧩 Challenge, think first, then reveal

Write is_palindrome(text) returning True when the string reads the same forwards and backwards; ignore letter case.

💡 Answer: def is_palindrome(text): return text.lower() == text.lower()[::-1], lowercasing symmetrizes the comparison, and the reversed slice [::-1] is the mirror image.

🧩 Challenge, think first, then reveal

Write fizzbuzz(n) returning a list from 1 to nn, replacing multiples of 3 with "Fizz", multiples of 5 with "Buzz", and multiples of both with "FizzBuzz".

💡 Answer: Multiples of both are multiples of $\mathrm{lcm}(3,5) = 15$, so test that case first: ["FizzBuzz" if i % 15 == 0 else "Fizz" if i % 3 == 0 else "Buzz" if i % 5 == 0 else i for i in range(1, n+1)].

🤔 Socratic Questions

  • Why must default parameters trail non-default ones? What would go wrong if the rule were reversed?
  • What does *args give you that a single list parameter does not? When would you reach for one over the other?
  • How does Python decide which definition applies when both def f(x) and def f(x, y=5) exist?

✅ Quick check

1. What does this return? def f(x, y=3): return x + y; f(5)

2. What is the output? def f(a, b=[]): b.append(a); return b; print(f(1)); print(f(2))

defparametersreturndocstrings