BitBootcampBitBootcamp ← all explainers 🔄 Review ⚙ Settings

PY-01-011-P · Primer — Functions & Arguments

The idea in one sentence

A function is a named, reusable block of code you define once with def and run whenever you need it, and arguments are the values you hand it each time you call it — passed either by position, by name, with sensible defaults, or scooped up in bulk.

Why it exists

The moment you write the same three lines twice, you have a problem: two copies to keep in sync, two places for a bug to hide, two chances to fix one and forget the other. Functions solve this by letting you name a chunk of logic once — def greet(...) — and then call it by that name as many times as you like, so the work lives in exactly one place. But a reusable block is only useful if you can feed it different inputs each time (greet Ada, then greet Bo), and that is what arguments are for: they are the slots you leave open when you define the function and fill in when you call it. Python gives you several ways to fill those slots — by their order, by their name, by leaning on a default, or by accepting "however many you throw at me" — because real code needs all of them: quick calls with a couple of positional values, self-documenting calls with named values, optional settings that usually stay the same, and flexible functions like print() that happily take one argument or twenty.

How it works

You define a function with def name(parameters):, put its body underneath (indented), and use return to hand a result back to whoever called it. The names in the parentheses are parameters; the actual values you pass in a call are arguments. You can pass arguments positionally (matched to parameters left-to-right) or as keyword arguments (matched by name, so order stops mattering). A parameter can have a default value with param=value, making it optional — callers who skip it get the default. And a *args parameter collects extra positional arguments into a tuple while **kwargs collects extra keyword arguments into a dict, so one function can accept any number of inputs:

def greet(name, greeting="Hello"):     # greeting has a default
    return f"{greeting}, {name}!"

print(greet("Ada"))                       # Hello, Ada!   (positional; default used)
print(greet("Ada", "Hi"))                 # Hi, Ada!      (both positional)
print(greet(greeting="Hey", name="Ada"))  # Hey, Ada!     (keyword; order flipped)

def total(*prices, tax=0.0, **labels):     # *args and **kwargs
    subtotal = sum(prices)                 # prices is a tuple: (10, 20, 30)
    return subtotal + subtotal * tax, labels

print(total(10, 20, 30, tax=0.1, note="lunch"))  # (66.0, {'note': 'lunch'})

Notice how greet(greeting="Hey", name="Ada") reads clearly even with the arguments out of order — that is the payoff of keyword arguments.

The one gotcha

Never use a mutable object (a list, dict, or set) as a default value. A default is evaluated once, when the function is defined — not fresh on every call — so a default list is created a single time and then quietly shared by every call that relies on it. The result looks haunted: values you added on one call show up on the next.

def add_item(item, cart=[]):     # DANGER: this list is created ONCE
    cart.append(item)
    return cart

print(add_item("apple"))    # ['apple']
print(add_item("banana"))   # ['apple', 'banana']  <- the apple is still here!

The fix is a five-line habit you'll use forever: default to None, then build the real object inside the function so each call gets its own.

def add_item(item, cart=None):
    if cart is None:
        cart = []          # fresh list every call
    cart.append(item)
    return cart

print(add_item("apple"))    # ['apple']
print(add_item("banana"))   # ['banana']  <- correct: independent carts

Immutable defaults (None, numbers, strings, True/False) are always safe — the trap is only mutable ones.

See it — how Python fills a function's parameters at call time

flowchart TD
    A["You call the function,<br/>e.g. greet('Ada')"] --> B{"Argument passed<br/>by name?"}
    B -->|"Yes (name=value)"| C["Match it to the<br/>parameter of that name"]
    B -->|"No"| D["Match it by position,<br/>left to right"]
    D --> E{"More positional args<br/>than parameters?"}
    E -->|"Yes"| F["Extras collected<br/>into *args tuple"]
    E -->|"No"| G["Parameter is filled"]
    C --> H{"Extra keyword args<br/>with no parameter?"}
    H -->|"Yes"| I["Extras collected<br/>into **kwargs dict"]
    H -->|"No"| G
    G --> J{"A parameter got<br/>no argument?"}
    J -->|"Has a default"| K["Use the default value"]
    J -->|"No default"| L["TypeError: missing<br/>required argument"]
    K --> M["Run the body,<br/>return a result"]
    F --> M
    I --> M

The one-line takeaway

def names reusable logic; arguments fill its parameters by position or by name, defaults make parameters optional, and *args/**kwargs accept any number of them — just never default a parameter to a list or dict.

Where you'll meet it

You already meet this every time you call print("hi", end="") — that end= is a keyword argument overriding a default, and print accepts any number of values because it uses *args under the hood. You'll write your own functions within your first hour of real Python, and the keyword-argument style becomes essential the moment a function has more than two or three settings: connect(host="localhost", port=5432, timeout=30) is readable in a way that connect("localhost", 5432, 30) never is. Every library you touch is built from these pieces — Flask routes, pandas read_csv with its dozens of optional keyword arguments, requests.get(url, params=..., headers=...) — so reading a function's signature and knowing which arguments are required, which are optional, and which get swept into **kwargs is a skill you'll use on literally every project. And the mutable-default gotcha is a genuine rite of passage: catching it (or spotting it in a code review) is one of the clearest signs you've stopped just using functions and started truly understanding them.

Named concepts: def, parameters, and return, positional vs keyword arguments, default values, *args and **kwargs