Functions: Parameters, Returns & Scope
Functions: Parameters, Returns & Scope
Functions are the most important concept in all of programming. They're how you stop writing the same code over and over, how you break complex problems into manageable pieces, and how professional code is organized.
If you get functions right, everything else becomes easier.
Defining Your First Function
def greet(name):
message = f"Hello, {name}!"
return message
# Call it
result = greet("Alice")
print(result) # Hello, Alice!
print(greet("Bob")) # Hello, Bob!
The anatomy: def keyword → function name → parameters in parentheses → colon → indented body → optional return.
Parameters vs Arguments
People use these interchangeably, but there's a distinction worth knowing:
def add(x, y): # x and y are PARAMETERS (in the definition)
return x + y
result = add(3, 5) # 3 and 5 are ARGUMENTS (in the call)
Default Parameter Values
def greet(name, greeting="Hello", punctuation="!"):
return f"{greeting}, {name}{punctuation}"
greet("Alice") # "Hello, Alice!"
greet("Alice", "Hi") # "Hi, Alice!"
greet("Alice", "Hey", ".") # "Hey, Alice."
greet("Alice", punctuation="?") # "Hello, Alice?" (keyword argument)
Rule: Parameters with defaults must come after parameters without defaults.
# Wrong
def bad(x="default", y): # SyntaxError!
pass
# Right
def good(y, x="default"):
pass
Return Values
A function can return anything — or nothing:
# Return multiple values (actually returns a tuple)
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([3, 1, 4, 1, 5, 9, 2])
print(f"Min: {low}, Max: {high}") # Min: 1, Max: 9
# Return nothing (returns None implicitly)
def log_message(msg):
print(f"[LOG] {msg}") # No return statement
result = log_message("Server started")
print(result) # None
*args and **kwargs
These let functions accept a variable number of arguments:
# *args — variable positional arguments (tuple inside function)
def total(*args):
return sum(args)
print(total(1, 2, 3)) # 6
print(total(10, 20, 30, 40)) # 100
# **kwargs — variable keyword arguments (dict inside function)
def print_info(**kwargs):
for key, value in kwargs.items():
print(f" {key}: {value}")
print_info(name="Alice", age=30, city="NYC")
# name: Alice
# age: 30
# city: NYC
# Combining all types
def flexible(required, *args, **kwargs):
print(f"Required: {required}")
print(f"Extra args: {args}")
print(f"Extra kwargs: {kwargs}")
flexible("must", 1, 2, 3, color="red", size=10)
Scope: Where Variables Live
This trips up many beginners. Python has rules about where a variable can be accessed:
x = "global" # Global scope — accessible everywhere
def outer():
y = "enclosing" # Enclosing scope
def inner():
z = "local" # Local scope — only inside inner()
print(x) # Can access global
print(y) # Can access enclosing
print(z) # Can access local
inner()
# print(z) # NameError! z doesn't exist here
outer()
The LEGB Rule — Python searches for variables in this order:
- Local (current function)
- Enclosing (outer functions)
- Global (module level)
- Built-in (Python built-ins like
len,print)
Modifying Global Variables
count = 0
def increment():
global count # Declare intent to modify global
count += 1
increment()
increment()
print(count) # 2
Tip: Using global is usually a code smell. Prefer passing values as parameters and returning them.
Lambda Functions
Lambdas are one-line anonymous functions. Use them for simple operations passed to other functions:
# Regular function
def double(x):
return x * 2
# Equivalent lambda
double = lambda x: x * 2
# Most useful when passed directly
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
sorted_names = sorted(["Alice", "bob", "Charlie"], key=lambda s: s.lower())
print(doubled) # [2, 4, 6, 8, 10]
print(evens) # [2, 4]
print(sorted_names) # ['Alice', 'bob', 'Charlie']
Docstrings: Document Your Functions
def calculate_compound_interest(principal, rate, years):
"""
Calculate compound interest.
Args:
principal: Initial investment amount
rate: Annual interest rate (as decimal, e.g., 0.07 for 7%)
years: Number of years
Returns:
Final amount including interest
"""
return principal * (1 + rate) ** years
help(calculate_compound_interest) # Shows your docstring
Real-World Example: Putting It Together
def process_student_grades(students):
"""Process student data and return summary statistics."""
grades = [s["grade"] for s in students]
passing = [s for s in students if s["grade"] >= 60]
return {
"total": len(students),
"average": sum(grades) / len(grades),
"pass_rate": len(passing) / len(students) * 100,
"top_student": max(students, key=lambda s: s["grade"])["name"],
}
students = [
{"name": "Alice", "grade": 92},
{"name": "Bob", "grade": 75},
{"name": "Carol", "grade": 55},
{"name": "Dave", "grade": 88},
]
summary = process_student_grades(students)
print(f"Class average: {summary['average']:.1f}")
print(f"Pass rate: {summary['pass_rate']:.0f}%")
print(f"Top student: {summary['top_student']}")
Key Principles for Good Functions
- One function, one job. A function called
get_usershould only get a user, not also send an email. - Name with verbs.
calculate_tax(),get_user(),validate_email()— the name says what it does. - Short is usually better. If a function is over 20 lines, consider splitting it.
- Don't repeat yourself (DRY). If you write the same code twice, make it a function.
Functions are not just a feature of Python — they're the fundamental building block of all software. Master them here, and every language you learn in the future will click faster.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises