Last 30 Days
No notifications
Functions are reusable blocks of code that perform a specific task. They help you write DRY (Don't Repeat Yourself) code, improve readability, and make programs modular.
def greet(name):
return f"Hello, {name}!"print(greet("Alice")) # Hello, Alice!
| Type | Syntax | Example |
| Positional | def f(a, b) | f(1, 2) |
| Default | def f(a, b=10) | f(1) → b is 10 |
| Keyword | def f(a, b) | f(b=2, a=1) |
| \*args | def f(*args) | Tuple of extra positional args |
| \*\*kwargs | def f(**kwargs) | Dict of extra keyword args |
def divide(a, b):
if b == 0:
return None
return a / b, a % b # Returns a tuplequotient, remainder = divide(17, 5)
Anonymous one-line functions:
square = lambda x: x ** 2
add = lambda a, b: a + bnums = [1, 2, 3, 4, 5]
evens = list(filter(lambda x: x % 2 == 0, nums))
doubled = list(map(lambda x: x * 2, nums))Decorators wrap a function to extend its behavior without modifying it:
def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper@logger
def add(a, b):
return a + b
A closure is a function that remembers variables from its enclosing scope:
def multiplier(factor):
def multiply(x):
return x * factor
return multiplydouble = multiplier(2)
print(double(5)) # 10
> Tip: Keep functions short and focused — each function should do one thing well. Use docstrings to document parameters and return values.
Functions are *named, reusable bundles of code*. Whenever you find yourself copy-pasting the same logic, that logic wants to be a function. Python's functions are also unusually flexible — first-class objects, default args, keyword-only args, decorators — so it pays to learn them properly.
def greet(name):
return f"hi, {name}"print(greet("Alice")) # hi, Alice
A function is an object created by def. It has a name, a parameter list, a body, and (optionally) a return value. Calling it runs the body with the arguments bound to parameters.
def order(item, qty=1, *, gift=False):
...order("book") # positional
order("book", 3) # positional + positional
order("book", qty=3) # positional + keyword
order("book", qty=3, gift=True) # full keyword
Key ideas:
qty=3).qty=1 makes qty optional.* must be passed by name.def divide(a, b):
if b == 0:
return None # explicit "no result"
return a / bdef stats(nums):
return min(nums), max(nums) # tuple — unpack at the call site
low, high = stats([3, 1, 4, 1, 5])
No return → returns None. Multiple values are returned as a tuple.
1. Mutable default arguments. def f(items=[]): shares one list across all calls. Use items=None and assign inside.
2. return inside a loop without thinking. A function exits as soon as return runs.
3. Missing return. If you forget it, the function returns None and the caller gets a confusing AttributeError later.
4. Shadowing builtins as parameter names. def f(list, dict, type): works but breaks readability; rename them.
5. Modifying caller's list "by accident". Lists are passed by reference — mutating inside the function changes it outside. Copy if you need isolation.
6. Calling a function vs referencing it. schedule(do_thing) vs schedule(do_thing()) — the former passes the function, the latter passes its result.
*args and **kwargsdef log(level, *messages, **fields):
print(level, messages, fields)log("INFO", "started", "port=8080", user="alice")
# INFO ('started', 'port=8080') {'user': 'alice'}
*args — collect extra positional args into a tuple.kwargs — collect extra keyword** args into a dict.f(*nums), f(**config).def discount(price: float, pct: float = 10.0) -> float:
"""Return price after applying a percentage discount."""
return price * (1 - pct / 100)Hints are optional but make IDE autocomplete and mypy/pyright checks much sharper. Docstrings (the triple-quoted line right after def) show up in help(fn) and on hover — future-you will thank present-you.
When Python looks up a name inside a function, it checks Local → Enclosing → Global → Built-in:
x = 10 # global
def outer():
x = 20 # enclosing
def inner():
# x = 30 # would shadow as local
print(x) # prints 20 (enclosing)
inner()global x — rebind the global from inside a function.nonlocal x — rebind the nearest enclosing x (used in closures).square = lambda x: x * x
sorted(users, key=lambda u: u["age"])lambda is a one-expression anonymous function. Use it for short callbacks; for anything multi-line, write a proper def.
Functions are objects — you can pass them around:
def apply_twice(fn, x):
return fn(fn(x))apply_twice(str.upper, "hi") # 'HI'
handlers = {"GET": handle_get, "POST": handle_post}
handlers[method](request)
Dispatch tables and callback patterns are everywhere in real code (sorting keys, event handlers, route maps).
nonlocaldef counter(start=0):
n = start
def step():
nonlocal n
n += 1
return n
return stepc = counter()
c(); c(); c() # 1, 2, 3
A closure is a function that captures variables from its enclosing scope. Useful for stateful callbacks without classes.
A decorator is a function that takes a function and returns a (usually wrapped) function. The @ syntax is sugar:
import functools, timedef timed(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
t = time.perf_counter()
out = fn(*args, **kwargs)
print(f"{fn.__name__} took {time.perf_counter()-t:.3f}s")
return out
return wrapper
@timed
def heavy(n):
return sum(range(n))
functools.wraps preserves the original name and docstring. Standard-library decorators worth knowing: @staticmethod, @classmethod, @property, @functools.lru_cache, @functools.cached_property, @dataclass.
def factorial(n):
return 1 if n <= 1 else n * factorial(n - 1)Python's default recursion depth is ~1000 (sys.getrecursionlimit()). For deeper logic, iterate instead. Python doesn't optimise tail calls.
A pure function:
1. Write mean(nums) and median(nums) with type hints and docstrings; test on a list and an empty list (handle the empty case explicitly).
2. Write a decorator @retry(times=3) that re-runs a function on exception up to N times; use it on a flaky function.
3. Build a closure-based make_counter() that returns a function whose count survives across calls.
4. Demonstrate the mutable-default trap and fix it with the None pattern.