Bytebook Compiler

Functions & Modules

Functions

Define reusable functions with def: parameters and return values, default and keyword arguments, *args and **kwargs, scope and the LEGB rule, and docstrings.

3 min read8 runnable examples4-question quiz
Python
def greet(name):
    """Return a friendly greeting."""          # docstring: documents the function
    return f"Hello, {name}!"

def area_and_perimeter(length, width):
    return length * width, 2 * (length + width)  # return several values (a tuple)

print(greet("Asha"))
a, p = area_and_perimeter(7, 3)
print(a, p)
print(greet.__doc__)
Output
Hello, Asha!
21 20
Return a friendly greeting.

A function without a return statement returns None.

Arguments: positional, keyword and default

Python
def simple_interest(principal, rate=7.5, years=1):
    return principal * rate * years / 100

print(simple_interest(10000))                      # defaults used
print(simple_interest(10000, 8))                   # positional
print(simple_interest(10000, years=3))             # keyword: skip rate
print(simple_interest(years=2, principal=5000))    # any order with keywords
Output
750.0
800.0
2250.0
750.0

*args and **kwargs

*args collects any number of extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict.

Python
def average(*numbers):
    return sum(numbers) / len(numbers) if numbers else 0

def make_profile(name, **details):
    lines = [f"Name: {name}"] + [f"{k.title()}: {v}" for k, v in details.items()]
    return "\n".join(lines)

print(average(4, 8, 15, 16, 23, 42))
print(make_profile("Ravi", course="CSE", year=2, city="Chennai"))

values = [3, 9, 27]
print(average(*values))                # * unpacks a list into arguments
Output
18.0
Name: Ravi
Course: CSE
Year: 2
City: Chennai
13.0

Scope: the LEGB rule

When Python looks up a name, it searches four scopes in order: Local → Enclosing (outer function) → Global (module) → Built-in.

Python
x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print("inner sees:", x)
    inner()
    print("outer sees:", x)

outer()
print("module sees:", x)

counter = 0
def increment():
    global counter          # needed to ASSIGN to a global
    counter += 1

increment(); increment()
print("counter =", counter)
Output
inner sees: local
outer sees: enclosing
module sees: global
counter = 2

nonlocal does the same for a variable in an enclosing function. Prefer returning values over modifying globals.

How arguments are passed

Python passes references to objects ("call by object reference"). If a function mutates a mutable argument, the caller sees the change; rebinding the parameter does not affect the caller.

Python
def mutate(lst):
    lst.append(99)          # changes the caller's list

def rebind(lst):
    lst = [0]               # only changes the local name

nums = [1, 2]
mutate(nums)
rebind(nums)
print(nums)
Output
[1, 2, 99]

Functions are objects

Functions can be stored in variables, passed to other functions and returned from them:

Python
def apply_twice(f, x):
    return f(f(x))

def make_multiplier(n):
    def multiply(x):
        return x * n        # remembers n: a closure
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)
print(apply_twice(double, 5), triple(7))

operations = {"+": lambda a, b: a + b, "*": lambda a, b: a * b}
print(operations["*"](6, 7))
Output
20 21
42

Type hints

Optional annotations document expected types (Python does not enforce them, but editors and checkers do):

Python
def bmi(weight_kg: float, height_m: float) -> float:
    return round(weight_kg / height_m ** 2, 1)

print(bmi(72.5, 1.68))
Output
25.7

Test yourself

4 questions · pick an answer to check it instantly

  1. 1

    What does a function return if it has no return statement?

  2. 2

    What is average(*[2, 4, 6]) if def average(*nums): return sum(nums) / len(nums)?

  3. 3

    What does global x inside a function allow?

  4. 4

    Why is def f(items=[]) dangerous?