Developer Tools

How to create functions in Python 3

Learn how to define, document, and test Python 3 functions with parameters, return values, scope rules, and practical examples.

By Inventive HQ Team

To create a function in Python 3, use the def keyword, a name, a parenthesised list of parameters, and a colon, then indent the body beneath it — and use return to hand a value back to the caller. For example, def greet(name): return f"Hello, {name}!" defines a one-line function you then invoke with greet("Sam"). The def line is the signature (name plus parameters), everything indented under it is the body, and return is what the call evaluates to. If you never write return, the function still hands back None.

That is the summary an AI Overview will give you. What it can't show you is the anatomy of that one line, the exact order parameters must appear in a real signature, or how a call travels in and a value travels back out. The animated diagram, the parameter-type decision table, and the copy-paste patterns below make those concrete.

Anatomy of a Python function and the call/return round trip A def line broken into keyword, name, parameters, and colon, with an arrow showing an argument passing in and a returned value travelling back out. def greet(name): def keyword greet name name parameter : colon return f"Hello, {name}!" indented body

greet( "Sam" ) argument passed in

call greet() return "Hello, Sam!"

Define once with def · call by name · a value flows back via return (or None)

The signature names the function and its parameters; the indented body runs on each call; an argument flows in and a value flows back out via return.
Advertisement

Why Functions Matter in Python 3

Functions let you group logic into reusable, testable units. They reduce duplication, clarify intent, and make it easier to evolve code as requirements change. Python 3 treats functions as first-class objects, meaning you can pass them around, assign them to variables, and even define functions inside other functions.

Defining Your First Function

Create a function with the def keyword, a name, optional parameters, and an indented body:

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

Call the function by using its name followed by parentheses:

>>> greet("Sam")
'Hello, Sam!'

If you do not explicitly return a value, Python returns None.

Parameters and Arguments

Python functions support several parameter types:

  • Positional parameters: standard arguments defined in order.
  • Keyword parameters: arguments supplied by name for readability.
  • Default parameters: provide fallback values.
  • Variadic parameters: *args collects extra positional arguments, **kwargs collects keyword arguments.
def log_event(message, level="INFO", *tags, **meta):
    print(level, message, tags, meta)

log_event("Signed in", "INFO", "auth", user="sarah")
# Output: INFO Signed in ('auth',) {'user': 'sarah'}

Use keyword-only parameters by placing a * in the signature:

def create_user(username, *, is_admin=False):
    ...

Which parameter type should I use?

The parameter types are not interchangeable, and they have to appear in a fixed order in the signature: positional/default first, then *args, then keyword-only, then **kwargs. Get the order wrong and Python raises a SyntaxError. This table shows when to reach for each.

Parameter typeSyntaxCaller passesReach for it when
Positionaldef f(a, b)f(1, 2)The first 1–2 required inputs whose meaning is obvious from order
Defaultdef f(a, b=10)f(1) or f(1, 5)An argument that has a sensible fallback most callers won't override
Keyword-onlydef f(a, *, flag=False)f(1, flag=True)Boolean/optional flags you never want set by accident of position
*argsdef f(*items)f(1, 2, 3)An unknown number of ordered values (like min, print)
**kwargsdef f(**opts)f(x=1, y=2)Arbitrary named options, or forwarding kwargs to another call

Rule of thumb: default to plain positional parameters, promote optional flags to keyword-only with a bare * for readability, and only add *args/**kwargs when the count is genuinely unbounded — they weaken editor autocomplete and type checking, so they are a cost, not a default.

Documenting Functions

Docstrings describe the intent, parameters, and return values. They power developer tools such as help() and IDE hints:

def convert_to_celsius(fahrenheit: float) -> float:
    """Convert a Fahrenheit temperature to Celsius."""
    return (fahrenheit - 32) * 5 / 9

Combine docstrings with type hints to communicate expectations without enforcing them at runtime.

Managing Scope and State

Variables defined inside a function are local and disappear once the function returns. To modify a global variable inside a function, declare it with global, though a better pattern is to return values or wrap state in classes.

Closures (functions defined inside other functions) remember the outer scope:

def multiplier(factor):
    def inner(value):
        return value * factor
    return inner

double = multiplier(2)
double(10)  # 20

Handling Errors and Edge Cases

Use raise to report invalid input and try/except blocks where the caller must recover gracefully:

def divide(numerator, denominator):
    if denominator == 0:
        raise ValueError("Denominator cannot be zero")
    return numerator / denominator

Design functions with predictable behavior—validate inputs, avoid hidden side effects, and document any exceptions that may propagate.

Testing Your Functions

Start with simple assertions or use pytest/unittest for structured testing:

def test_convert_to_celsius():
    assert convert_to_celsius(212) == 100
    assert round(convert_to_celsius(32), 2) == 0

Automated tests catch regressions when you refactor or optimize logic. Pair tests with static analysis tools (e.g., mypy, ruff) to enforce consistent signatures and type usage.

Best Practices Checklist

  1. Keep functions focused. Aim for single-responsibility behavior.
  2. Name functions clearly. Choose verbs for actions (send_email) and nouns for factories (user_from_row).
  3. Limit the parameter count. If signatures grow too large, pass a dataclass or configuration object.
  4. Avoid mutable default arguments. Use None sentinels and initialize inside the function.
  5. Return explicit results. Prefer returning values over mutating global state.
  6. Add docstrings and type hints. Make the interface self-documenting for teammates and tooling.

Functions are the building blocks of maintainable Python. By mastering signatures, docstrings, error handling, and test coverage, you establish a foundation that scales from quick scripts to production-grade applications.

Frequently Asked Questions

How do you define a function in Python 3?

Use the def keyword followed by a name, a parenthesised parameter list, and a colon, then indent the body beneath it: def greet(name): return f"Hello, {name}!". The name should be a lowercase verb phrase, parameters go inside the parentheses, and everything indented under the def line is the function body. Nothing runs until you call the function by name with parentheses, e.g. greet("Sam").

What does a Python function return if there is no return statement?

It returns None. Every Python function returns a value; if you never write an explicit return, or you write a bare return with no expression, Python substitutes the singleton None. This is why calling a print-only helper and assigning its result gives you None rather than the printed text.

What is the difference between *args and **kwargs?

*args collects any extra positional arguments into a tuple, while **kwargs collects any extra keyword arguments into a dictionary. Use *args when a function should accept a variable number of ordered values and **kwargs when callers may pass arbitrary named options. In a signature they must appear in the order: standard parameters, *args, keyword-only parameters, then **kwargs.

Why should you avoid mutable default arguments in Python?

Default argument values are evaluated once, when the function is defined, not each time it is called. A mutable default such as def f(items=[]) therefore shares the same list across every call, so appended values leak between invocations. Use None as the sentinel and create the container inside the body: def f(items=None): items = items or [].

What is the difference between a parameter and an argument?

A parameter is the name in the function definition (the placeholder), and an argument is the actual value you pass when you call the function. In def greet(name), name is a parameter; in greet("Sam"), "Sam" is the argument bound to that parameter.

How do you write a keyword-only argument in Python?

Place a bare * in the signature before the parameters you want to force to be keyword-only: def create_user(username, *, is_admin=False). Anything after the * can no longer be passed positionally, which prevents callers from accidentally flipping a boolean flag by position and makes call sites self-documenting.

What is a docstring and where does it go?

A docstring is a string literal placed as the very first statement inside a function body, wrapped in triple quotes. Python stores it on the function's __doc__ attribute and surfaces it through help(), IDE tooltips, and documentation generators. Describe what the function does, its parameters, and what it returns.

Do Python type hints enforce types at runtime?

No. Annotations like def add(x: int, y: int) -> int are documentation and tooling metadata only; Python does not check or coerce them when the code runs. Static analysers such as mypy and Pyright read the hints to catch mismatches before you ship, but at runtime you can still pass a string to an int parameter without an error.

What is a closure in Python?

A closure is a function defined inside another function that remembers the variables from the enclosing scope even after the outer function has returned. In def multiplier(factor): def inner(v): return v * factor; return inner, the returned inner keeps a reference to factor, so multiplier(2) produces a function that always doubles its input.

pythonfunctionsprogrammingsoftware development