Functions let you bundle logic into reusable blocks, name it, and call it with different inputs. They are the main way to structure Python code. This guide covers how to define and call functions, use parameters and return values, and avoid common mistakes like mutable default arguments or confusing None returns. For more advanced patterns see *args and **kwargs and decorators.
1. Why use functions
Without functions, you repeat the same code in several places. When you need to change the behaviour, you must find and update every copy. A function has one definition and many call sites; changing the definition fixes or improves behaviour everywhere. Functions also give you a clear name for "what this does," which makes programs easier to read and test. Once you know data types and conditionals, functions are the next step to organising programs.
2. Defining and calling a function
Use def followed by the function name, parentheses with optional parameters, and a colon. The next lines are the function body, indented. To run the code, call the function by name with arguments in parentheses.
def greet(name):
return f"Hello, {name}!"
message = greet("Alice") # "Hello, Alice!"
print(greet("Bob")) # Hello, Bob!
return sends a value back to the caller. If you do not use return, or you use return without a value, the function returns None. That often leads to TypeError: object of type 'NoneType' has no len() or AttributeError: 'NoneType' object has no attribute '...' when you assume the function returned a list or object. Always return something when the caller expects a value.
3. Parameters and arguments
Parameters are the names listed in the def; arguments are the values you pass when you call the function. Positional arguments are matched by order; keyword arguments are matched by name. You can mix both: positional first, then keyword.
def describe(name, age, city="Unknown"):
return f"{name}, {age}, from {city}"
describe("Alice", 30) # city defaults to "Unknown"
describe("Bob", 25, "London") # city = "London"
describe("Charlie", 22, city="Paris") # keyword argument
Default parameter values are evaluated once when the function is defined, not on each call. Do not use mutable defaults (e.g. def f(x=[])); the same list is reused for every call. Use None and assign inside the function: def f(x=None): x = x or []. See Default arguments.
4. Return values
A function can return one value with return value. To return multiple values, return a tuple (or list) and unpack at the call site: a, b = f(). Returning nothing or return alone returns None. If the caller uses the result in an expression (e.g. len(f())), and f() returns None, you get a TypeError. Check that every code path in the function either returns a value or clearly documents that it returns None.
def min_max(numbers):
if not numbers:
return None
return min(numbers), max(numbers)
low, high = min_max([3, 1, 4, 1, 5]) # low=1, high=5
result = min_max([]) # None
5. Scope and names
Variables assigned inside a function are local to that function. Reading a name looks in the local scope first, then enclosing (e.g. outer function), then global, then built-in. If you assign to a name anywhere in the function, that name is treated as local for the whole function—so using it before assignment causes UnboundLocalError. Use global or nonlocal only when you really need to change an outer variable; prefer passing values in and returning them.
6. Practical examples
Functions are ideal for validation, formatting, and small reusable steps. For example, a function that returns a list or None should be used with care:
def get_first(items):
return items[0] if items else None
first = get_first([10, 20, 30]) # 10
first = get_first([]) # None
# len(get_first([])) # TypeError: NoneType has no len()
Another common pattern is building a result in a loop and returning it:
def squares(n):
result = []
for i in range(n):
result.append(i ** 2)
return result
# Or: return [i ** 2 for i in range(n)]
7. *args and **kwargs (brief)
To accept any number of positional arguments, use *args; for keyword arguments, use **kwargs. The names are conventional; the important part is the single * or double **. For full detail and examples see *args and **kwargs.
def log(*args, **kwargs):
print("Args:", args)
print("Kwargs:", kwargs)
log(1, 2, 3, name="Alice") # args=(1,2,3), kwargs={"name":"Alice"}
8. Best practices
- Keep functions short and focused; one main task per function.
- Return values instead of mutating global state when possible.
- Do not use mutable default arguments; use
Noneand assign inside the body. - Document what the function returns when it is not obvious (especially
None). - Use type hints when you want to document parameter and return types (see Type hints).
9. Next steps
Use functions with lists and loops to process data. Read Return values and Scope and namespace for deeper coverage. When you see NoneType has no len() or UnboundLocalError, check return paths and where variables are assigned. Move on to Classes and objects when you need to group data and behaviour together.