Functions, Parameters & Variable Scope in Python
As scripts grow past a few dozen lines, repeating identical calculations in multiple places quickly makes code brittle. Functions let you bundle reusable logic behind a descriptive name, accept inputs cleanly, and return predictable results without duplicating code.
1. Defining and Calling Functions with def
A Python function begins with the def keyword, a lowercase snake_case name, optional parameters inside parentheses, and a colon. Code inside the function does not run when defined—it executes only when you call the function by name:
def format_greeting(name, track="Python"):
return f"Welcome {name}! You are enrolled in {track}."
message = format_greeting("Ananya")
print(message)
2. Parameters, Default Arguments, and Return Values
- Positional vs. keyword arguments: You can pass values in order (
calculate_discount(500, 15)) or explicitly by parameter name (calculate_discount(price=500, percent=15)) for extra clarity. - Default parameter values: Defining
track="Python"makes the second argument optional whenever callers omit it. returnvs.print(): Printing displays text strictly for humans in the terminal and evaluates toNone. Usingreturnpasses the actual computed data back so callers can store it in variables, test it, or pass it to another function.
3. Variable Scope: Local vs. Global (LEGB Rule)
Variables created inside a function belong strictly to that function's local scope—they appear when the function starts and disappear as soon as it returns. Variables defined at module top level exist in global scope.
| Scope Level | Where Defined | Visibility |
|---|---|---|
| Local (L) | Inside the current def body |
Only inside that single function call. |
| Enclosing (E) | Outer wrapper function containing a nested function | Readable by inner nested functions. |
| Global (G) | Top level of the .py file |
Readable module-wide; prefer passing inputs via parameters instead of mutating globals. |
| Built-in (B) | Preloaded Python namespace (len, sum, print) |
Available everywhere unless shadowed by a local variable. |
4. Flexible Parameters (*args, **kwargs) & Returning Multiple Values
Sometimes you want a utility helper that accepts any number of positional arguments (like average(10, 20, 30)) or optional keyword settings. Python packs extra positional values into a tuple via *args and extra keyword pairs into a dictionary via **kwargs. Separately returning multiple comma-separated outputs packs them cleanly as a tuple:
def analyze_scores(*scores: int) -> tuple[int, float]:
"""Return highest score and rounded average for any count of scores."""
if not scores:
return 0, 0.0
return max(scores), round(sum(scores) / len(scores), 2)
top, mean = analyze_scores(78, 92, 85, 96)
print(f"Top: {top}, Average: {mean}")
Notice the optional type hints (*scores: int and -> tuple[int, float]) and triple-quoted docstring right inside the function definition—these self-document your code and enable IDE autocomplete and static checking.
5. Watch Out for Mutable Default Arguments
Never use a mutable literal like an empty list (items=[]) or dictionary (cache={}) directly as a default parameter value. Python evaluates default expressions only once when defining the function, causing repeated calls to accidentally share the same list across calls! Instead, default to None and initialize inside the body:
def add_lesson(title, syllabus=None):
if syllabus is None:
syllabus = []
syllabus.append(title)
return syllabus
6. Hands-On Function Practice & Key Takeaways
Open CodingRider's Online Compiler snippet below and write a reusable function celsius_to_fahrenheit(c: float) -> float paired with a short docstring, plus an optional precision: int = 1 keyword parameter returning values rounded cleanly via round().
- Keep functions focused on one clear job: Small, pure helper functions that take explicit arguments and return computed outputs are dramatically easier to test and reuse across web routes, CLI scripts, and unit suites than long procedural blocks mutating global variables.
- Prefer
returnover printing inside business logic: Printing directly inside helper calculations locks output strictly to the terminal; returning structured values lets callers render HTML templates, JSON responses, or terminal logs seamlessly. - Avoid mutable defaults permanently: Use
param=Nonewhenever defaulting lists or dictionaries so independent function calls never leak state between users or requests.
def calculate_discount(price, percent=10):
"""Return final price after applying a percentage discount."""
discount_amount = price * (percent / 100)
return round(price - discount_amount, 2)
print("Default 10% off 500:", calculate_discount(500))
print("Custom 25% off 800:", calculate_discount(800, percent=25))