Python Functions — The Complete Notebook
Comprehensive guide on Python Functions — The Complete Notebook.
Python Functions
1. Overview#
Functions are the first level of abstraction in Python — a way to name a piece of behavior so it can be reused, tested, and reasoned about independently. This note covers function anatomy, scope rules, closures, and functions as first-class objects. (Decorators — functions that wrap other functions — get their own dedicated note: decorators.md.)
2. Defining Functions#
🐍 PythonInteractive WebAssemblydef greet(name):
"""Return a friendly greeting for the given name."""
return f"Hello, {name}!"
print(greet("Kamal"))
print(greet.__doc__) # Docstrings are accessible at runtime — useful for tooling
2.1 Type Hints#
Type hints don't change runtime behavior, but they make intent explicit and enable static analysis tools like mypy.
🐍 PythonInteractive WebAssemblydef calculate_total(price: float, quantity: int, discount: float = 0.0) -> float:
return (price * quantity) * (1 - discount)
total: float = calculate_total(499.0, 3, discount=0.1)
3. Argument Types in Depth#
🐍 PythonInteractive WebAssemblydef build_request(url, method="GET", *args, timeout=30, **headers):
print(f"URL: {url}")
print(f"Method: {method}")
print(f"Extra positional args: {args}")
print(f"Timeout: {timeout}")
print(f"Headers: {headers}")
build_request(
"https://api.example.com",
"POST",
"extra1", "extra2",
timeout=10,
Authorization="Bearer token123"
)
| Syntax | Name | Behavior |
|---|---|---|
def f(a, b) | Positional-or-keyword | Can be passed either way |
def f(a=1) | Default argument | Used if caller omits the value |
def f(*args) | Variadic positional | Collects extras into a tuple |
def f(**kwargs) | Variadic keyword | Collects extras into a dict |
def f(a, /, b) | Positional-only | a cannot be passed as a=value (3.8+) |
def f(*, b) | Keyword-only | b must be passed as b=value |
🐍 PythonInteractive WebAssemblydef move_point(x, y, /, *, label):
return f"{label}: ({x}, {y})"
move_point(3, 4, label="origin") # OK
# move_point(x=3, y=4, label="origin") # TypeError — x, y are positional-only
3.1 Unpacking Arguments When Calling#
🐍 PythonInteractive WebAssemblydef add(a, b, c):
return a + b + c
values = [1, 2, 3]
print(add(*values)) # unpack list into positional args
kwargs = {"a": 1, "b": 2, "c": 3}
print(add(**kwargs)) # unpack dict into keyword args
4. Return Values & Unpacking#
🐍 PythonInteractive WebAssemblydef min_max(numbers):
return min(numbers), max(numbers) # returns a tuple
lowest, highest = min_max([4, 8, 15, 16, 23, 42])
print(lowest, highest) # 4 42
🐍 PythonInteractive WebAssemblydef get_stats(numbers):
return {
"count": len(numbers),
"sum": sum(numbers),
"avg": sum(numbers) / len(numbers),
}
stats = get_stats([10, 20, 30])
print(stats["avg"]) # 20.0
5. Scope: The LEGB Rule#
Python resolves a variable name by checking scopes in this order: Local → Enclosing → Global → Built-in.
🐍 PythonInteractive WebAssemblyx = "global x"
def outer():
x = "enclosing x"
def inner():
x = "local x"
print(x) # local x — Local scope wins first
inner()
print(x) # enclosing x
outer()
print(x) # global x
5.1 global and nonlocal#
🐍 PythonInteractive WebAssemblycounter = 0
def increment():
global counter
counter += 1
increment()
increment()
print(counter) # 2
def make_counter():
count = 0
def increment():
nonlocal count # modifies the enclosing variable, not global
count += 1
return count
return increment
counter_fn = make_counter()
print(counter_fn()) # 1
print(counter_fn()) # 2
Reaching for
globalis usually a sign the design could be improved — passing state explicitly or using a class is often clearer and safer in larger programs.
6. Closures#
A closure is a function that "remembers" variables from the scope it was created in, even after that scope has finished executing.
🐍 PythonInteractive WebAssemblydef make_multiplier(factor):
def multiplier(value):
return value * factor # factor is "closed over"
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10)) # 20
print(triple(10)) # 30
print(double.__closure__[0].cell_contents) # 2
Practical use case — configuration factories:
🐍 PythonInteractive WebAssemblydef make_validator(min_val, max_val):
def validate(value):
return min_val <= value <= max_val
return validate
is_valid_age = make_validator(0, 120)
print(is_valid_age(25)) # True
print(is_valid_age(200)) # False
7. Lambda Functions#
Anonymous, single-expression functions — best for short, throwaway logic passed to another function.
🐍 PythonInteractive WebAssemblyemployees = [
{"name": "Asha", "salary": 72000},
{"name": "Ravi", "salary": 65000},
{"name": "Meera", "salary": 81000},
]
top_earners = sorted(employees, key=lambda e: e["salary"], reverse=True)
print(top_earners[0]["name"]) # Meera
If a lambda needs more than one line of logic or a name to explain itself, write a regular
deffunction instead — lambdas should stay trivial.
8. Functions as First-Class Objects#
Functions in Python are objects — they can be assigned to variables, stored in data structures, and passed around like any other value.
🐍 PythonInteractive WebAssemblydef celsius_to_fahrenheit(c):
return c * 9 / 5 + 32
def fahrenheit_to_celsius(f):
return (f - 32) * 5 / 9
converters = {
"c_to_f": celsius_to_fahrenheit,
"f_to_c": fahrenheit_to_celsius,
}
print(converters["c_to_f"](100)) # 212.0
8.1 Higher-Order Functions: map, filter, functools.reduce#
🐍 PythonInteractive WebAssemblyfrom functools import reduce
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda n: n ** 2, numbers))
evens = list(filter(lambda n: n % 2 == 0, numbers))
total = reduce(lambda acc, n: acc + n, numbers, 0)
print(squared) # [1, 4, 9, 16, 25]
print(evens) # [2, 4]
print(total) # 15
A list comprehension is usually more Pythonic than
map/filterfor simple cases:[n ** 2 for n in numbers]reads more naturally thanmap(lambda n: n ** 2, numbers).
9. Recursion#
🐍 PythonInteractive WebAssemblydef factorial(n):
if n <= 1: # base case — stops the recursion
return 1
return n * factorial(n - 1) # recursive case
print(factorial(5)) # 120
🐍 PythonInteractive WebAssemblydef fibonacci(n, memo={}):
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo)
return memo[n]
print(fibonacci(30)) # fast, thanks to memoization
Python has a default recursion limit (
sys.getrecursionlimit(), usually 1000). Deep recursion in Python is often less efficient than an equivalent loop — use recursion where it makes the logic clearer, not by default.
10. Generators & yield#
Generators produce values lazily, one at a time, instead of building an entire list in memory — essential for large or infinite data streams.
🐍 PythonInteractive WebAssemblydef read_large_file_lines(filepath):
with open(filepath) as f:
for line in f:
yield line.strip()
def fibonacci_sequence():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci_sequence()
first_10 = [next(fib) for _ in range(10)]
print(first_10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
| Feature | List | Generator |
|---|---|---|
| Memory | Stores all items at once | Produces one item at a time |
| Reusable | Yes, iterate repeatedly | No, exhausted after one pass |
| Use case | Small/medium datasets | Streaming, large/infinite data |
10.1 Generator Expressions#
🐍 PythonInteractive WebAssemblysquares = (n ** 2 for n in range(1_000_000)) # lazy — no memory spike
print(next(squares)) # 0
print(sum(squares)) # sums the rest without ever building a full list
11. Summary & Best Practices Checklist#
- Use type hints on functions in shared/production code — they double as documentation.
- Keep functions small and focused on one responsibility.
- Avoid
global; prefer passing state explicitly or using closures/classes. - Reach for a closure when you need a function "pre-configured" with some state.
- Keep lambdas to single, trivial expressions — use
defotherwise. - Prefer comprehensions over
map/filterfor simple transformations. - Use generators instead of lists when working with large or streaming data.
- Always define a clear base case before writing recursive logic.
Functions, Closures & LEGB Scope Checkpoint
Finished studying this notebook?
Mark this guide as completed to update your course progress roadmap.