Elena' s AI Blog

Python Functions: Writing Reusable Code

12 May 2026 (updated: 01 Jun 2026) / 14 minutes to read

Elena Daehnhardt


Generated by Midjourney. Prompt: Developer using Codex AI CLI in a dark terminal.


TL;DR:
  • Functions let you write code once and reuse it everywhere. Learn def, parameters, return values, default arguments, *args/**kwargs, type hints, and lambda expressions — the tools that make your Python programs clean, readable, and maintainable.

Previous: Part 1 — Python Programming Language

Next: Part 3 — Python Error Handling: When Birds Misbehave

Introduction

In my first Python post we covered variables, lists, dictionaries, and list comprehensions — the data and control flow that let you write a working script. In the OOP post we jumped all the way to classes. But there is an important stop in between, and that stop is functions.

Functions are how you stop writing the same thing twice. They are the reason a 500-line program does not become a 5,000-line program. They are also the first step toward thinking about code as something you design rather than something you just write. Once functions feel natural, classes make much more sense — a class is largely just a collection of functions that share some data.

We will keep our birds. They are patient, useful, and by now familiar.

What Is a Function?

A function is a named block of code you can call by name, pass data into, and get a result back from. In Python you define one with def:

def greet_bird(name):
    print(f"Hello, {name}!")

greet_bird("Eagle")
greet_bird("Pigeon")
Hello, Eagle!
Hello, Pigeon!

That is the whole idea. Write the logic once inside def, then call it as many times as you need. Without functions we would have to repeat print(f"Hello, ...") every time — tedious, error-prone, and hard to change later.

Parameters and Return Values

Functions become genuinely useful when they take inputs and give something back. The return statement sends a value back to the caller:

def describe_bird(name, color, can_fly):
    status = "can fly" if can_fly else "cannot fly"
    return f"A {color} {name} that {status}."

print(describe_bird("Penguin", "black and white", False))
print(describe_bird("Eagle", "brown", True))
A black and white Penguin that cannot fly.
A brown Eagle that can fly.

The variables name, color, and can_fly are called parameters — placeholders that receive the values you pass in. The values you pass when calling the function ("Penguin", "black and white", False) are called arguments.

A function can return any Python object: a number, a string, a list, a dictionary, even another function. If you do not write a return statement, Python returns None silently.

Default Arguments

Sometimes a parameter has a sensible default value and you do not want to have to type it every time. Python lets you set defaults in the function signature:

def feed_bird(name, food="seeds", amount=100):
    print(f"Feeding {name} with {amount}g of {food}.")

feed_bird("Pigeon")
feed_bird("Parrot", food="fruit")
feed_bird("Eagle", food="fish", amount=300)
Feeding Pigeon with 100g of seeds.
Feeding Parrot with 100g of fruit.
Feeding Eagle with 300g of fish.

Default arguments must come after non-default ones in the signature. This does not work:

# Wrong — default before non-default
def feed_bird(food="seeds", name):  # SyntaxError
    ...

One important gotcha: never use a mutable object (a list, a dictionary) as a default argument. Python creates the default once, not on each call, so every call shares the same object and mutations accumulate in surprising ways. Use None and create the object inside the function instead:

# Wrong
def add_bird(bird, flock=[]):
    flock.append(bird)
    return flock

# Right
def add_bird(bird, flock=None):
    if flock is None:
        flock = []
    flock.append(bird)
    return flock

*args and **kwargs

Sometimes you want a function to accept a variable number of arguments. Two special syntaxes handle this.

*args collects any number of positional arguments into a tuple:

def count_birds(*birds):
    print(f"You have {len(birds)} birds: {', '.join(birds)}.")

count_birds("Eagle", "Pigeon")
count_birds("Eagle", "Pigeon", "Stork", "Swan", "Penguin")
You have 2 birds: Eagle, Pigeon.
You have 5 birds: Eagle, Pigeon, Stork, Swan, Penguin.

**kwargs collects any number of keyword arguments into a dictionary:

def bird_profile(name, **attributes):
    print(f"\n{name}:")
    for key, value in attributes.items():
        print(f"  {key}: {value}")

bird_profile("Eagle", color="brown", wingspan=200, can_fly=True)
bird_profile("Penguin", color="black and white", can_swim=True, speed_kmh=3)
Eagle:
  color: brown
  wingspan: 200
  can_fly: True

Penguin:
  color: black and white
  can_swim: True
  speed_kmh: 3

You can combine all of these in one function signature. The order must be: regular parameters, then *args, then keyword-only parameters, then **kwargs:

def full_report(location, *birds, season="summer", **notes):
    print(f"Location: {location}, Season: {season}")
    print(f"Birds spotted: {', '.join(birds)}")
    for key, value in notes.items():
        print(f"  Note — {key}: {value}")

full_report("Amsterdam", "Heron", "Coot", season="spring", weather="rainy")
Location: Amsterdam, Season: spring
Birds spotted: Heron, Coot
  Note — weather: rainy

Type Hints

Python is dynamically typed, which means a function will happily accept the wrong type of argument and fail later in a confusing way. Type hints let you document what types a function expects and returns. They are optional and not enforced at runtime, but they make code much easier to read and they let tools like mypy or your IDE catch type errors before you run anything:

def calculate_flight_time(distance_km: float, speed_kmh: float) -> float:
    """Return flight time in hours for a given distance and speed."""
    return distance_km / speed_kmh

hours = calculate_flight_time(200.0, 55.0)
print(f"Flight time: {hours:.2f} hours")
Flight time: 3.64 hours

The -> float after the parentheses declares the return type. The docstring inside triple quotes documents what the function does — a habit worth forming from the beginning, because future-you will thank present-you.

For more complex types you can import from typing (Python 3.8 and earlier) or use the built-in generics (Python 3.9+):

# Python 3.9+
def filter_flying_birds(birds: list[str], can_fly: dict[str, bool]) -> list[str]:
    return [b for b in birds if can_fly.get(b, False)]

birds = ["Eagle", "Penguin", "Pigeon", "Ostrich"]
flight_map = {"Eagle": True, "Penguin": False, "Pigeon": True, "Ostrich": False}
print(filter_flying_birds(birds, flight_map))
['Eagle', 'Pigeon']

Lambda Functions

A lambda is a small anonymous function written in a single expression. It is useful when you need a short function as an argument to another function and do not want to define a full def for something so brief:

birds = [
    {"name": "Eagle",   "wingspan": 200},
    {"name": "Pigeon",  "wingspan": 50},
    {"name": "Stork",   "wingspan": 110},
    {"name": "Swan",    "wingspan": 240},
]

# Sort by wingspan using a lambda as the key
sorted_birds = sorted(birds, key=lambda b: b["wingspan"])
for bird in sorted_birds:
    print(f"{bird['name']}: {bird['wingspan']} cm")
Pigeon: 50 cm
Stork: 110 cm
Eagle: 200 cm
Swan: 240 cm

Use lambda sparingly. When the logic is more than a single expression, a named def is always clearer. A well-named function is its own documentation; a complex lambda is just confusion waiting to happen.

Putting It Together: A Bird Feeding Station

Here is a slightly more realistic example that combines everything from this post — a simple bird feeding station manager:

from datetime import datetime

def record_feeding(
    bird_name: str,
    food: str = "seeds",
    amount_g: float = 100.0,
    *observers: str,
    location: str = "garden",
    **weather,
) -> dict:
    """
    Record a bird feeding event and return a log entry.

    Args:
        bird_name: Name of the bird being fed.
        food: Type of food provided.
        amount_g: Amount of food in grams.
        *observers: Names of people who witnessed the feeding.
        location: Where the feeding took place.
        **weather: Arbitrary weather conditions as key-value pairs.

    Returns:
        A dictionary representing the feeding log entry.
    """
    return {
        "timestamp": datetime.now().isoformat(),
        "bird": bird_name,
        "food": food,
        "amount_g": amount_g,
        "observers": list(observers),
        "location": location,
        "weather": weather,
    }

entry = record_feeding(
    "Pigeon",
    "mixed grain",
    150.0,
    "Elena", "Anna",
    location="park",
    temperature_c=18,
    wind="light breeze",
)

for key, value in entry.items():
    print(f"{key}: {value}")
timestamp: 2026-05-10T09:14:02.341
bird: Pigeon
food: mixed grain
amount_g: 150.0
observers: ['Elena', 'Anna']
location: park
weather: {'temperature_c': 18, 'wind': 'light breeze'}

One function, fully documented, handles a wide range of inputs, and returns structured data ready to be stored, logged, or passed to the next function in your program.

Conclusion

Functions are one of those things that feel slightly unnecessary when you first learn them — your script works fine without them — and then indispensable once you have used them for a week. They are how you turn a collection of lines into a program you can actually maintain and build on.

In the next post in this series we will look at what happens when things go wrong: error handling, exceptions, and how to write Python that fails gracefully rather than crashing in the most confusing way possible. The birds will be involved, and some of them will misbehave.

Did you like this post? Please let me know if you have any comments or suggestions — always happy to hear from you!

References

  1. Python Documentation — Defining Functions
  2. PEP 484 — Type Hints
  3. PEP 3107 — Function Annotations
  4. Python Basics — Syntax and Data Structures
  5. Python Classes and OOP
desktop bg dark

About Elena

Elena, a PhD in Computer Science, simplifies AI concepts and helps you use machine learning.

Citation
Elena Daehnhardt. (2026) 'Python Functions: Writing Reusable Code', daehnhardt.com, 12 May 2026. Available at: https://daehnhardt.com/blog/2026/05/12/python-functions/
All Posts