Python Functions
Learn how to define and call Python functions, use parameters, default arguments, *args, **kwargs, return values, scope, and docstrings.
A function is a named, reusable block of code that performs a specific task. Functions let you write logic once and call it from anywhere in your program — making your code shorter, easier to read, and simpler to test. This chapter covers everything you need to work confidently with Python functions: definition, parameters, return values, default arguments, *args and **kwargs, scope, docstrings, type hints, recursion, and first-class function usage.
Defining a Function
Use the def keyword, a name, parentheses, and a colon. The body is indented one level.
def greet(name):
print("Hello, " + name)The function is not executed until you call it. Nothing happens at definition time except that Python stores the function object under the given name.
Calling a Function
Pass arguments inside the parentheses matching the parameter names in the definition.
Return Values
A function can send a result back to the caller with the return statement. Without return, Python returns None.
def add(a, b):
return a + b
result = add(3, 5)
print(result) # 8Once return executes, the function stops immediately — any code after it in the same function body is unreachable.
Returning Multiple Values
Python lets you return several values as a tuple, which you can unpack on the calling side.
import math
def circle_stats(radius):
area = math.pi * radius ** 2
circumference = 2 * math.pi * radius
return area, circumference
area, circ = circle_stats(5)
print(round(area, 2)) # 78.54
print(round(circ, 2)) # 31.42Parameters and Arguments
| Term | Meaning |
|---|---|
| Parameter | Variable name in the function definition |
| Argument | Actual value passed when calling the function |
Python supports several ways to pass arguments.
Positional Arguments
Arguments are matched to parameters in order.
def describe(name, age):
print(name, "is", age, "years old")
describe("Bob", 25) # Bob is 25 years oldKeyword Arguments
You can pass arguments by name, in any order.
describe(age=25, name="Bob") # Bob is 25 years oldDefault Parameter Values
Provide a fallback value that is used when the caller omits that argument.
def greet(name="World"):
print("Hello, " + name)
greet() # Hello, World
greet("Alice") # Hello, AliceGotcha: never use a mutable object (list, dict) as a default value — it is created once at definition time and shared across all calls. Use None as the default and create the object inside the body instead.
# Correct pattern for a mutable default
def append_item(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lstArbitrary Positional Arguments (*args)
Prefix a parameter name with * to collect any number of positional arguments into a tuple.
def total(*numbers):
return sum(numbers)
print(total(1, 2, 3)) # 6
print(total(10, 20)) # 30Arbitrary Keyword Arguments (**kwargs)
Prefix with ** to collect any number of keyword arguments into a dictionary.
def describe_person(**info):
for key, value in info.items():
print(key + ": " + str(value))
describe_person(name="Alice", age=30, city="Paris")
# name: Alice
# age: 30
# city: ParisCombining Parameter Types
When you mix parameter types, the order must be: positional, *args, keyword-only, **kwargs.
def log(level, *messages, separator=" | ", **meta):
print(level.upper(), separator.join(messages), meta)
log("info", "started", "ready", separator=" — ", version="1.0")
# INFO started — ready {'version': '1.0'}Variable Scope
Python's scoping rules follow the LEGB rule: Local → Enclosing → Global → Built-in.
A variable defined inside a function is local — it cannot be accessed outside.
def my_func():
x = 10 # local to my_func
print(x)
my_func()
# print(x) # NameError: name 'x' is not definedTo read or modify a global variable inside a function, declare it with global.
x = 10
def change_x():
global x
x = 20
change_x()
print(x) # 20Use global sparingly — functions that depend on global state are harder to test and reuse. See Python Variables and Global Variables for a deeper look at scope.
Docstrings
A docstring is a string literal placed immediately after the def line. It documents what the function does, what it accepts, and what it returns. Python stores it in the function's __doc__ attribute and tools like help() display it.
def add(a, b):
"""Return the sum of a and b.
Args:
a: First number.
b: Second number.
Returns:
The sum as a number.
"""
return a + b
print(add.__doc__)
# Return the sum of a and b.
# ...Type Hints
Type hints (PEP 484) let you annotate parameter and return types. Python does not enforce them at runtime, but editors, linters, and mypy use them to catch bugs early.
def multiply(a: float, b: float) -> float:
return a * b
print(multiply(3, 4)) # 12
print(multiply(2.5, 2.0)) # 5.0Recursion
A function can call itself. This is called recursion and is useful for problems with a naturally recursive structure (trees, factorials, etc.). Every recursive function needs a base case that stops the recursion.
def factorial(n):
if n == 0 or n == 1: # base case
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
print(factorial(0)) # 1Python limits recursion depth (default 1000) to prevent stack overflows. For deep recursion, prefer an iterative approach or use sys.setrecursionlimit.
Functions as First-Class Objects
In Python, functions are objects. You can store them in variables, pass them as arguments, and return them from other functions.
def apply(func, value):
return func(value)
def double(n):
return n * 2
print(apply(double, 7)) # 14This is the foundation for lambda functions, higher-order functions, and decorators.
Built-in Functions vs. User-Defined Functions
Python ships with many built-in functions — print(), len(), range(), sum(), sorted() — that are always available without an import. User-defined functions are the ones you write with def. Both follow identical call syntax.
Best Practices
- Name functions as verbs:
calculate_tax()is clearer thantax(). - Do one thing: a function that validates, saves, and sends an email is three functions waiting to be born.
- Write a docstring: even a single sentence describing the purpose is valuable.
- Avoid side effects where possible: functions that return values and change no global state are easier to test.
- Keep signatures short: more than three or four parameters is a signal to group related data into a class or dict.
- Use type hints: they serve as lightweight documentation and enable static analysis.