Python Functions
Functions are reusable blocks of code that perform a specific task. They help you organize code, avoid repetition, and make programs easier to read and maintain.
Defining Functions
# Basic function definition
def greet():
print("Hello, World!")
# Call the function
greet() # Hello, World!
# Function with parameters
def greet_person(name):
print(f"Hello, {name}!")
greet_person("Alice") # Hello, Alice!Parameters and Arguments
Positional Arguments
def add(a, b):
return a + b
result = add(3, 5)
print(result) # 8Keyword Arguments
def create_user(name, age, city):
print(f"{name}, {age}, from {city}")
# Using keyword arguments (order doesn't matter)
create_user(age=30, city="NYC", name="Alice")
# Alice, 30, from NYC
# Mix positional and keyword (positional must come first)
create_user("Bob", city="London", age=25)
# Bob, 25, from LondonDefault Parameters
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice") # Hello, Alice!
greet("Bob", "Good morning") # Good morning, Bob!Warning: Never use mutable objects as default parameters:
# BAD - the list persists between calls!
def add_item_bad(item, items=[]):
items.append(item)
return items
print(add_item_bad("a")) # ['a']
print(add_item_bad("b")) # ['a', 'b'] - Unexpected!
# GOOD - use None and create inside
def add_item_good(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item_good("a")) # ['a']
print(add_item_good("b")) # ['b'] - Correct!*args and **kwargs
*args - Variable Positional Arguments
def sum_all(*args):
"""Accepts any number of positional arguments."""
print(f"Type: {type(args)}") # <class 'tuple'>
return sum(args)
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
# With regular parameters
def multiply(factor, *numbers):
return [factor * n for n in numbers]
print(multiply(2, 1, 2, 3)) # [2, 4, 6]**kwargs - Variable Keyword Arguments
def print_info(**kwargs):
"""Accepts any number of keyword arguments."""
print(f"Type: {type(kwargs)}") # <class 'dict'>
for key, value in kwargs.items():
print(f" {key}: {value}")
print_info(name="Alice", age=30, city="NYC")
# name: Alice
# age: 30
# city: NYCCombining All Parameter Types
def example(a, b, *args, key1="default", **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"key1={key1}")
print(f"kwargs={kwargs}")
example(1, 2, 3, 4, key1="custom", x=10, y=20)
# a=1, b=2
# args=(3, 4)
# key1=custom
# kwargs={'x': 10, 'y': 20}Order of parameters: regular → *args → keyword-only → **kwargs
Return Values
Single Return
def square(n):
return n ** 2
result = square(5)
print(result) # 25Multiple Return Values
def divide(a, b):
quotient = a // b
remainder = a % b
return quotient, remainder # Returns a tuple
q, r = divide(17, 5)
print(f"17 ÷ 5 = {q} remainder {r}") # 17 ÷ 5 = 3 remainder 2Early Return
def find_first_negative(numbers):
for num in numbers:
if num < 0:
return num
return None # No negative found
print(find_first_negative([1, 3, -5, 7])) # -5
print(find_first_negative([1, 2, 3])) # NoneNote: Functions without a return statement (or with bare return) return None.
Variable Scope
# Global variable
name = "Global"
def outer():
# Enclosing variable
name = "Outer"
def inner():
# Local variable
name = "Inner"
print(f"Inner: {name}")
inner()
print(f"Outer: {name}")
outer()
print(f"Global: {name}")
# Inner: Inner
# Outer: Outer
# Global: GlobalThe LEGB Rule
Python looks up variables in this order:
| Scope | Description |
|---|---|
| Local | Inside the current function |
| Enclosing | Inside enclosing functions |
| Global | Module-level variables |
| Built-in | Python's built-in names (print, len, etc.) |
Lambda Functions
Anonymous, single-expression functions:
# Regular function
def double(x):
return x * 2
# Lambda equivalent
double = lambda x: x * 2
print(double(5)) # 10
# Lambda with multiple parameters
add = lambda a, b: a + b
print(add(3, 4)) # 7
# Common use: sorting
students = [("Alice", 90), ("Bob", 85), ("Charlie", 92)]
# Sort by score
students.sort(key=lambda s: s[1])
print(students)
# [('Bob', 85), ('Alice', 90), ('Charlie', 92)]
# Sort by name (descending)
students.sort(key=lambda s: s[0], reverse=True)
print(students)
# [('Charlie', 92), ('Bob', 85), ('Alice', 90)]
# With built-in functions
numbers = [1, -2, 3, -4, 5]
positives = list(filter(lambda x: x > 0, numbers))
print(positives) # [1, 3, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(squared) # [1, 4, 9, 16, 25]Higher-Order Functions
Functions that accept or return other functions:
# Function as argument
def apply_operation(func, x, y):
return func(x, y)
def add(a, b):
return a + b
def multiply(a, b):
return a * b
print(apply_operation(add, 3, 4)) # 7
print(apply_operation(multiply, 3, 4)) # 12
# Function returning a function
def multiplier(factor):
def multiply(n):
return n * factor
return multiply
double = multiplier(2)
triple = multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15Built-in Higher-Order Functions
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# map() - apply function to each element
squared = list(map(lambda x: x ** 2, numbers))
print(squared) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# filter() - keep elements where function returns True
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6, 8, 10]
# reduce() - accumulate a single value
from functools import reduce
total = reduce(lambda a, b: a + b, numbers)
print(total) # 55
# sorted() with key function
words = ["banana", "apple", "cherry", "date"]
by_length = sorted(words, key=len)
print(by_length) # ['date', 'apple', 'banana', 'cherry']Type Hints
Python 3.5+ supports type hints for better documentation and IDE support:
def add(a: int, b: int) -> int:
return a + b
def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! ") * times
def process_items(items: list[str]) -> dict[str, int]:
return {item: len(item) for item in items}
# Optional type
from typing import Optional
def find_user(user_id: int) -> Optional[dict]:
"""Returns user dict or None if not found."""
users = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
return users.get(user_id)Note: Type hints are not enforced at runtime. They're for documentation and tools like mypy.
Practical Example: Student Grade Calculator
"""
Student grade calculator using functions.
"""
def calculate_average(*scores: float) -> float:
"""Calculate the average of given scores."""
if not scores:
return 0.0
return sum(scores) / len(scores)
def determine_grade(average: float) -> str:
"""Determine letter grade from average score."""
if average >= 90:
return "A"
elif average >= 80:
return "B"
elif average >= 70:
return "C"
elif average >= 60:
return "D"
else:
return "F"
def format_report(name: str, scores: list[float]) -> str:
"""Generate a formatted grade report."""
avg = calculate_average(*scores)
grade = determine_grade(avg)
lines = [
f"Student: {name}",
f"Scores: {', '.join(str(s) for s in scores)}",
f"Average: {avg:.1f}",
f"Grade: {grade}",
]
width = max(len(line) for line in lines) + 4
border = "=" * width
report = f"\n{border}\n"
for line in lines:
report += f" {line}\n"
report += border
return report
# Usage
students = {
"Alice": [92, 88, 95, 91],
"Bob": [78, 82, 75, 80],
"Charlie": [95, 98, 92, 97],
}
for name, scores in students.items():
print(format_report(name, scores))Summary
- Functions are defined with
def function_name(parameters): - Support positional, keyword, and default parameters
*argscollects extra positional arguments as a tuple**kwargscollects extra keyword arguments as a dictionary- Functions can return multiple values (as tuples)
- Lambda functions are anonymous single-expression functions
- Python follows the LEGB rule for variable scope
- Higher-order functions accept or return other functions
- Use type hints for documentation and IDE support
- Never use mutable objects as default parameter values
Next, we'll learn about Python lists.