This module is your comprehensive guide to understanding and mastering functions in Python. Functions are the building blocks of organized, reusable, and efficient code. Whether you're just starting your Python journey or looking to deepen your understanding, this tutorial will provide clear explanations and a range of code examples from beginner-friendly to advanced.
By the end of this module, you'll be able to write your own functions, understand how data flows in and out of them, and leverage advanced functional programming concepts. Let's dive in!
Defining and Calling Functions in Python
Functions are named blocks of code that perform a specific task. They allow you to break down complex problems into smaller, manageable pieces, making your code easier to write, read, and maintain. Think of a function as a mini-program within your main program.
The def
Keyword: Your Function's Starting Point
The def
keyword is how you define a function in Python. It stands for "define" and signals to Python that you're about to create a new function.
Note: Python function definition, define function, create function Python, def
keyword in Python.
Beginner-Friendly Examples:
Python
# Example 1: A very simple function that prints a greeting
# This function takes no input and simply performs an action.
def greet_user():
print("Hello, Python learner!")
# To use the function, you need to "call" it.
# Calling a function means executing the code inside it.
greet_user() # Output: Hello, Python learner!
print("-" * 30)
# Example 2: A slightly more interactive greeting
def greet_name():
name = input("What's your name? ")
print(f"Nice to meet you, {name}!")
# Call the function to see it in action
# greet_name() # Uncomment to run and test
print("-" * 30)
Intermediate Examples:
Python
# Example 3: A function to perform a simple calculation
# Functions can encapsulate small, repeatable tasks.
def calculate_sum():
num1 = 10
num2 = 20
total = num1 + num2
print(f"The sum is: {total}")
calculate_sum() # Output: The sum is: 30
print("-" * 30)
# Example 4: A function that describes an animal
# Functions help in organizing related operations.
def describe_animal():
animal = "cat"
sound = "meow"
print(f"The {animal} says {sound}.")
describe_animal() # Output: The cat says meow.
print("-" * 30)
Advanced Examples:
Python
# Example 5: Defining a function that does nothing (a placeholder)
# The 'pass' keyword is a null operation; it does nothing.
# It's useful when you're structuring your code and plan to add logic later.
def future_feature():
pass # This function will be implemented later.
# You can call it, but nothing will happen.
future_feature()
print("Future feature function called (it does nothing yet).")
print("-" * 30)
# Example 6: A function that defines another function (nested function)
# While less common for beginners, this demonstrates advanced scope concepts.
def outer_function():
print("Inside the outer function.")
def inner_function():
print("Inside the inner function.")
inner_function() # The inner function can only be called from within the outer function.
outer_function()
# inner_function() # This would cause an error because inner_function is not defined in the global scope.
print("-" * 30)
# Example 7: Using type hints in function definition for clarity
# Type hints improve code readability and help with static analysis.
def welcome_message(name: str) -> None:
"""
This function prints a welcome message to a given name.
'name: str' indicates 'name' is expected to be a string.
'-> None' indicates the function doesn't return a value.
"""
print(f"Welcome, {name}!")
welcome_message("Alice") # Output: Welcome, Alice!
print("-" * 30)
Parameters and Arguments: Giving Your Functions Data
Functions often need information to perform their tasks. This information is passed into the function using parameters and arguments.
- Parameters are the placeholders for values that a function expects to receive. They are defined in the function's
def
statement. - Arguments are the actual values that are passed to the function when it is called.
Note: Python function parameters, function arguments, pass data to function, def
with parameters.
Beginner-Friendly Examples:
Python
# Example 1: Function with one parameter
# 'name' is a parameter that acts as a placeholder for the user's name.
def say_hello(name):
print(f"Hello, {name}!")
# When calling the function, "Alice" is the argument.
say_hello("Alice") # Output: Hello, Alice!
say_hello("Bob") # Output: Hello, Bob!
print("-" * 30)
# Example 2: Function with two parameters for a simple sum
# 'a' and 'b' are parameters.
def add_numbers(a, b):
sum_result = a + b
print(f"The sum of {a} and {b} is: {sum_result}")
# 5 and 3 are arguments.
add_numbers(5, 3) # Output: The sum of 5 and 3 is: 8
add_numbers(10, 25) # Output: The sum of 10 and 25 is: 35
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Function to calculate the area of a rectangle
# Parameters 'length' and 'width' make the function versatile.
def calculate_rectangle_area(length, width):
area = length * width
print(f"A rectangle with length {length} and width {width} has an area of: {area}")
calculate_rectangle_area(7, 4) # Output: A rectangle with length 7 and width 4 has an area of: 28
calculate_rectangle_area(10.5, 6) # Output: A rectangle with length 10.5 and width 6 has an area of: 63.0
print("-" * 30)
# Example 4: Function to check if a number is even or odd
# The 'number' parameter allows us to test different inputs.
def check_even_odd(number):
if number % 2 == 0:
print(f"{number} is an even number.")
else:
print(f"{number} is an odd number.")
check_even_odd(4) # Output: 4 is an even number.
check_even_odd(7) # Output: 7 is an odd number.
print("-" * 30)
Advanced Examples:
Python
# Example 5: Function with multiple parameters and data validation
# Demonstrates how parameters can be used to control complex logic.
def create_user_profile(username, email, age):
if not isinstance(username, str) or not username:
print("Error: Username must be a non-empty string.")
return # Exit the function early on error
if not isinstance(email, str) or "@" not in email:
print("Error: Invalid email format.")
return
if not isinstance(age, int) or age < 0:
print("Error: Age must be a non-negative integer.")
return
print(f"Profile created for {username}: Email - {email}, Age - {age}")
create_user_profile("johndoe", "john@example.com", 30)
# Output: Profile created for johndoe: Email - john@example.com, Age - 30
create_user_profile(123, "invalid", -5)
# Output: Error: Username must be a non-empty string.
print("-" * 30)
# Example 6: Passing a list as an argument
# Functions can operate on complex data structures passed as arguments.
def process_list(data_list):
print(f"Original list: {data_list}")
squared_list = [x**2 for x in data_list]
print(f"Squared list: {squared_list}")
my_numbers = [1, 2, 3, 4, 5]
process_list(my_numbers)
# Output:
# Original list: [1, 2, 3, 4, 5]
# Squared list: [1, 4, 9, 16, 25]
print("-" * 30)
# Example 7: Using type hints for clarity with parameters
# Enhances readability and helps tools detect potential issues.
def calculate_discount(price: float, discount_percentage: float) -> float:
"""
Calculates the discounted price.
Args:
price (float): The original price of the item.
discount_percentage (float): The discount percentage (e.g., 0.10 for 10%).
Returns:
float: The price after applying the discount.
"""
if not (0 <= discount_percentage <= 1):
print("Warning: Discount percentage should be between 0 and 1.")
return price # Return original price if discount is invalid
discount_amount = price * discount_percentage
final_price = price - discount_amount
return final_price
original_price = 100.0
discount = 0.20 # 20% discount
final_price_calculated = calculate_discount(original_price, discount)
print(f"Original price: ${original_price:.2f}, Discount: {discount*100:.0f}%, Final price: ${final_price_calculated:.2f}")
# Output: Original price: $100.00, Discount: 20%, Final price: $80.00
print("-" * 30)
The return
Statement: Getting Results Back from Functions
While print()
displays output to the console, the return
statement is how a function sends a value back to the part of the code that called it. This is crucial for functions that compute values that you want to use later in your program. If a function doesn't explicitly return
a value, it implicitly returns None
.
Note: Python return
statement, function return value, getting output from function, None
return.
Beginner-Friendly Examples:
Python
# Example 1: Function returning a single value
# This function calculates a sum and 'returns' it, making it available for use.
def add_two_numbers(num1, num2):
total = num1 + num2
return total # The 'total' value is sent back.
# We can store the returned value in a variable.
result = add_two_numbers(10, 5)
print(f"The sum is: {result}") # Output: The sum is: 15
# We can also use the returned value directly.
print(f"Double the sum: {add_two_numbers(4, 6) * 2}") # Output: Double the sum: 20
print("-" * 30)
# Example 2: Function that returns a greeting string
# The function prepares a message and returns it.
def create_greeting(name):
message = f"Hello, {name}! Welcome."
return message
my_greeting = create_greeting("Charlie")
print(my_greeting) # Output: Hello, Charlie! Welcome.
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Function returning a boolean value
# Useful for conditional checks.
def is_adult(age):
if age >= 18:
return True # Returns True if the condition is met
else:
return False # Returns False otherwise
print(f"Is 20 an adult? {is_adult(20)}") # Output: Is 20 an adult? True
print(f"Is 16 an adult? {is_adult(16)}") # Output: Is 16 an adult? False
print("-" * 30)
# Example 4: Function returning multiple values (as a tuple)
# Python allows returning multiple values which are packed into a tuple.
def get_user_info():
name = "Pythonista"
age = 5
city = "Codeville"
return name, age, city # Returns a tuple (name, age, city)
# You can unpack the returned tuple into separate variables.
user_name, user_age, user_city = get_user_info()
print(f"User: {user_name}, Age: {user_age}, City: {user_city}")
# Output: User: Pythonista, Age: 5, City: Codeville
print("-" * 30)
Advanced Examples:
Python
# Example 5: Early return for error handling or specific conditions
# Improves function efficiency by exiting once a condition is met.
def divide_numbers(numerator, denominator):
if denominator == 0:
print("Error: Cannot divide by zero!")
return None # Return None to indicate an error or invalid operation
return numerator / denominator
result1 = divide_numbers(10, 2)
print(f"10 / 2 = {result1}") # Output: 10 / 2 = 5.0
result2 = divide_numbers(7, 0) # Output: Error: Cannot divide by zero!
print(f"7 / 0 = {result2}") # Output: 7 / 0 = None
print("-" * 30)
# Example 6: Function returning another function (Higher-Order Function concept)
# This is an advanced concept, but demonstrates the flexibility of 'return'.
def create_multiplier(factor):
def multiplier(number):
return number * factor
return multiplier # Returns the 'multiplier' function itself
# Now we can create custom multiplier functions
double = create_multiplier(2)
triple = create_multiplier(3)
print(f"Double of 5: {double(5)}") # Output: Double of 5: 10
print(f"Triple of 5: {triple(5)}") # Output: Triple of 5: 15
print("-" * 30)
# Example 7: Using return with complex data structures (dictionary)
# Functions can return any valid Python object.
def analyze_text(text):
words = text.split()
num_words = len(words)
num_chars = len(text)
unique_words = len(set(words))
return {
"word_count": num_words,
"character_count": num_chars,
"unique_word_count": unique_words,
"first_word": words[0] if words else None
}
text_data = "This is a sample text for analysis. This text is quite simple."
analysis_results = analyze_text(text_data)
print(f"Text analysis results: {analysis_results}")
# Output:
# Text analysis results: {'word_count': 11, 'character_count': 56, 'unique_word_count': 9, 'first_word': 'This'}
print("-" * 30)
Function Arguments: Different Ways to Pass Data
Python offers flexible ways to pass arguments to functions, allowing you to create more readable and robust code.
Positional Arguments: Order Matters!
Positional arguments are the most common type. The order in which you pass the arguments during a function call must match the order of the parameters in the function definition.
Note: Positional arguments Python, function argument order, default argument passing.
Beginner-Friendly Examples:
Python
# Example 1: Two positional arguments
# The order of 'name' and 'age' is fixed.
def introduce_person(name, age):
print(f"Hello, my name is {name} and I am {age} years old.")
introduce_person("Alice", 30) # "Alice" maps to 'name', 30 maps to 'age'
introduce_person("Bob", 25)
print("-" * 30)
# Example 2: Simple arithmetic with positional arguments
# 'num1' and 'num2' are processed in the order they are received.
def subtract_numbers(num1, num2):
result = num1 - num2
print(f"{num1} - {num2} = {result}")
subtract_numbers(10, 5) # 10 is num1, 5 is num2
subtract_numbers(5, 10) # Order matters: 5 is num1, 10 is num2, result will be negative
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Function to display product details
# All arguments must be provided in the correct order.
def display_product(product_name, price, quantity):
print(f"Product: {product_name}, Price: ${price:.2f}, Quantity: {quantity}")
display_product("Laptop", 1200.50, 1)
display_product("Mouse", 25.00, 5)
print("-" * 30)
# Example 4: Calculating BMI (Body Mass Index)
# height and weight are positional arguments.
def calculate_bmi(weight_kg, height_m):
bmi = weight_kg / (height_m ** 2)
print(f"Weight: {weight_kg}kg, Height: {height_m}m, BMI: {bmi:.2f}")
calculate_bmi(70, 1.75) # Output: Weight: 70kg, Height: 1.75m, BMI: 22.86
calculate_bmi(85, 1.80) # Output: Weight: 85kg, Height: 1.8m, BMI: 26.23
print("-" * 30)
Advanced Examples:
Python
# Example 5: Handling multiple positional arguments for a complex calculation
# The order is strictly followed for formula application.
def calculate_compound_interest(principal, rate, time, compounds_per_year):
# A = P * (1 + R/N)^(NT)
amount = principal * (1 + rate / compounds_per_year)**(compounds_per_year * time)
interest = amount - principal
print(f"Principal: ${principal:.2f}, Rate: {rate*100}%, Time: {time} years, Compounds per year: {compounds_per_year}")
print(f"Total Amount: ${amount:.2f}, Total Interest: ${interest:.2f}")
calculate_compound_interest(1000, 0.05, 10, 12) # $1000 at 5% for 10 years, compounded monthly
# Output:
# Principal: $1000.00, Rate: 5.0%, Time: 10 years, Compounds per year: 12
# Total Amount: $1647.01, Total Interest: $647.01
print("-" * 30)
# Example 6: Positional arguments in a lambda function (though less common for multiple)
# For simple operations where order is clear.
# This one is a bit of a stretch for "advanced positional arguments" but demonstrates the concept.
area_of_triangle = lambda base, height: 0.5 * base * height
print(f"Area of triangle (base 10, height 5): {area_of_triangle(10, 5)}") # Output: Area of triangle (base 10, height 5): 25.0
print("-" * 30)
# Example 7: Using positional arguments in a function that modifies a list
# The list and index are positional.
def update_list_element(my_list, index, new_value):
if 0 <= index < len(my_list):
my_list[index] = new_value
print(f"List after update: {my_list}")
else:
print("Error: Index out of bounds.")
my_data = [10, 20, 30, 40]
update_list_element(my_data, 1, 25) # my_data is modified in place
update_list_element(my_data, 5, 99) # Error: Index out of bounds.
print("-" * 30)
Keyword Arguments: Clarity and Flexibility
Keyword arguments allow you to pass arguments to a function by explicitly naming the corresponding parameter. This improves readability, especially for functions with many parameters, and allows you to pass arguments in any order.
Note: Keyword arguments Python, named arguments, Python function clarity, pass arguments by name.
Beginner-Friendly Examples:
Python
# Example 1: Using keyword arguments for better readability
# Even though the order is different, keywords ensure correct mapping.
def describe_car(make, model, year):
print(f"This is a {year} {make} {model}.")
# Positional call (order matters)
describe_car("Toyota", "Camry", 2020) # Output: This is a 2020 Toyota Camry.
# Keyword call (order doesn't matter, clarity improves)
describe_car(year=2022, model="Civic", make="Honda") # Output: This is a 2022 Honda Civic.
describe_car(model="Mustang", year=1967, make="Ford") # Output: This is a 1967 Ford Mustang.
print("-" * 30)
# Example 2: Keyword arguments with mixed positional and keyword
# Positional arguments must come before keyword arguments.
def order_pizza(size, topping1, topping2):
print(f"You ordered a {size} pizza with {topping1} and {topping2}.")
order_pizza("large", topping2="pepperoni", topping1="mushrooms")
# Output: You ordered a large pizza with mushrooms and pepperoni.
# 'large' is positional, 'topping2' and 'topping1' are keywords.
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Function with many parameters benefiting from keyword arguments
# Makes function calls much clearer.
def configure_email_sender(sender_address, recipient_address, subject, body, attachment=None):
print(f"Sending email from: {sender_address}")
print(f"To: {recipient_address}")
print(f"Subject: {subject}")
print(f"Body: {body[:30]}...") # Show first 30 chars of body
if attachment:
print(f"Attachment: {attachment}")
else:
print("No attachment.")
configure_email_sender(
recipient_address="user@example.com",
subject="Important Update",
body="Dear user, please find the latest updates attached.",
sender_address="admin@mycompany.com"
)
# Output:
# Sending email from: admin@mycompany.com
# To: user@example.com
# Subject: Important Update
# Body: Dear user, please find the lat...
# No attachment.
print("-" * 30)
# Example 4: Combining positional and keyword arguments for flexibility
# Positional arguments are filled first, then keywords.
def student_enrollment(student_id, course_name, semester="Fall", year=2024):
print(f"Student ID: {student_id}")
print(f"Enrolled in: {course_name}")
print(f"For Semester: {semester}, Year: {year}")
student_enrollment(101, "Introduction to Python")
# Output:
# Student ID: 101
# Enrolled in: Introduction to Python
# For Semester: Fall, Year: 2024 (using defaults)
student_enrollment(205, "Advanced Algorithms", year=2025, semester="Spring")
# Output:
# Student ID: 205
# Enrolled in: Advanced Algorithms
# For Semester: Spring, Year: 2025
print("-" * 30)
Advanced Examples:
Python
# Example 5: Enforcing keyword-only arguments using '*'
# Any arguments after '*' in parameters must be passed as keywords.
def create_report(title, *, data, author="Admin", date_created=None):
import datetime
if date_created is None:
date_created = datetime.date.today()
print(f"--- Report: {title} ---")
print(f"Author: {author}")
print(f"Date: {date_created}")
print(f"Data: {data}")
# create_report("Sales Overview", ["Q1 Data"], "John Doe") # This would raise an error because 'data' and 'author' are positional
create_report("Sales Overview", data=["Q1 Data"], author="John Doe")
# Output:
# --- Report: Sales Overview ---
# Author: John Doe
# Date: 2025-06-13
# Data: ['Q1 Data']
create_report("Project Progress", data={"tasks_completed": 5, "total_tasks": 10})
# Output:
# --- Report: Project Progress ---
# Author: Admin
# Date: 2025-06-13
# Data: {'tasks_completed': 5, 'total_tasks': 10}
print("-" * 30)
# Example 6: Passing a dictionary as keyword arguments using '**' (unpacking)
# This is powerful for dynamic function calls.
def configure_server(host, port, protocol="HTTP", timeout=30):
print(f"Configuring server: Host={host}, Port={port}, Protocol={protocol}, Timeout={timeout}s")
server_config = {
"host": "localhost",
"port": 8080,
"protocol": "HTTPS",
"timeout": 60
}
configure_server(**server_config) # The '**' unpacks the dictionary into keyword arguments.
# Output: Configuring server: Host=localhost, Port=8080, Protocol=HTTPS, Timeout=60s
# You can also override values or add new ones:
configure_server(**{"host": "remote_server", "port": 9000}, protocol="FTP")
# Output: Configuring server: Host=remote_server, Port=9000, Protocol=FTP, Timeout=30s
print("-" * 30)
# Example 7: Using keyword arguments in conjunction with type hints
# Enhances clarity and maintainability for complex interfaces.
def process_user_input(user_id: int, message: str, is_urgent: bool = False) -> None:
"""Processes user input, optionally marking it as urgent."""
status = "Urgent" if is_urgent else "Normal"
print(f"[{status}] User {user_id}: {message}")
process_user_input(user_id=123, message="System startup complete.")
# Output: [Normal] User 123: System startup complete.
process_user_input(message="Critical error detected!", user_id=456, is_urgent=True)
# Output: [Urgent] User 456: Critical error detected!
print("-" * 30)
Default Arguments: Making Parameters Optional
Default arguments allow you to provide a default value for a parameter. If the caller doesn't provide an argument for that parameter, the default value is used. If an argument is provided, it overrides the default.
Note: Default arguments Python, optional parameters, Python function default values, flexible function calls.
Beginner-Friendly Examples:
Python
# Example 1: A simple default argument
# 'greeting' has a default value of "Hello".
def say_something(message, greeting="Hello"):
print(f"{greeting}, {message}!")
say_something("world") # Uses the default greeting: Output: Hello, world!
say_something("everyone", "Hi") # Overrides the default: Output: Hi, everyone!
print("-" * 30)
# Example 2: Default argument for a power function
# 'power' defaults to 2 (squaring).
def calculate_power(base, power=2):
result = base ** power
print(f"{base} raised to the power of {power} is: {result}")
calculate_power(5) # Output: 5 raised to the power of 2 is: 25 (uses default power)
calculate_power(2, 3) # Output: 2 raised to the power of 3 is: 8 (overrides power)
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Multiple default arguments
# Default arguments must always be placed after non-default arguments.
def create_file(filename, content="", encoding="utf-8"):
try:
with open(filename, "w", encoding=encoding) as f:
f.write(content)
print(f"File '{filename}' created successfully.")
except Exception as e:
print(f"Error creating file: {e}")
create_file("my_document.txt") # Creates empty file with default encoding
# Output: File 'my_document.txt' created successfully.
create_file("my_report.txt", content="This is a sample report.")
# Output: File 'my_report.txt' created successfully.
create_file("special_chars.txt", content="été", encoding="latin-1")
# Output: File 'special_chars.txt' created successfully.
print("-" * 30)
# Example 4: Using default arguments for flags or options
# Boolean flags are common use cases for defaults.
def send_notification(message, level="info", log_to_file=False):
print(f"[{level.upper()}] Notification: {message}")
if log_to_file:
with open("notifications.log", "a") as log_file:
log_file.write(f"[{level.upper()}] {message}\n")
print("Notification logged to file.")
send_notification("System is running normally.")
# Output: [INFO] Notification: System is running normally.
send_notification("Critical error detected!", level="error", log_to_file=True)
# Output:
# [ERROR] Notification: Critical error detected!
# Notification logged to file.
# (Also writes to notifications.log)
print("-" * 30)
Advanced Examples:
Python
# Example 5: Caution with mutable default arguments!
# Default arguments are evaluated once when the function is defined.
# If a mutable object (like a list or dictionary) is used as a default,
# subsequent calls without providing that argument will share the *same* object.
def add_item_to_list(item, my_list=[]): # DANGER: mutable default
my_list.append(item)
return my_list
list1 = add_item_to_list("apple")
print(f"List 1: {list1}") # Output: List 1: ['apple']
list2 = add_item_to_list("banana")
print(f"List 2: {list2}") # Output: List 2: ['apple', 'banana'] - NOT ['banana']!
# CORRECT WAY to handle mutable defaults:
def add_item_to_list_safe(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list
list3 = add_item_to_list_safe("apple")
print(f"List 3: {list3}") # Output: List 3: ['apple']
list4 = add_item_to_list_safe("banana")
print(f"List 4: {list4}") # Output: List 4: ['banana'] - This is the desired behavior!
print("-" * 30)
# Example 6: Default arguments with complex objects (e.g., datetime)
# Again, consider the evaluation time of the default.
import datetime
def log_event(message, timestamp=None):
# If no timestamp is provided, use the current time.
if timestamp is None:
timestamp = datetime.datetime.now()
print(f"[{timestamp}] {message}")
log_event("Application started.")
log_event("User logged in.", timestamp=datetime.datetime(2025, 1, 1, 10, 0, 0))
print("-" * 30)
# Example 7: Using default arguments for configuration settings
# Functions can offer default configurations that can be overridden.
def connect_to_database(db_name="mydb", user="guest", password=""):
print(f"Attempting to connect to database '{db_name}' with user '{user}'.")
if password:
print("Password provided.")
else:
print("No password.")
# Simulate connection logic
return True
connect_to_database() # Uses all defaults
# Output:
# Attempting to connect to database 'mydb' with user 'guest'.
# No password.
connect_to_database(db_name="production_db", user="admin", password="secure_password")
# Output:
# Attempting to connect to database 'production_db' with user 'admin'.
# Password provided.
print("-" * 30)
Arbitrary Arguments (*args
, **kwargs
): Handling Flexible Inputs
Sometimes you don't know in advance how many arguments a function will receive. Python provides special syntax to handle an arbitrary number of positional arguments (*args
) and keyword arguments (**kwargs
).
*args
: Collects an arbitrary number of positional arguments into a tuple.**kwargs
: Collects an arbitrary number of keyword arguments into a dictionary.
Note: *args
Python, **kwargs
Python, arbitrary positional arguments, arbitrary keyword arguments, flexible function arguments.
Beginner-Friendly Examples:
Python
# Example 1: Using *args to sum an unknown number of numbers
# `*numbers` will collect all positional arguments into a tuple.
def sum_all_numbers(*numbers):
total = 0
for num in numbers:
total += num
print(f"The sum of {numbers} is: {total}")
sum_all_numbers(1, 2, 3) # Output: The sum of (1, 2, 3) is: 6
sum_all_numbers(10, 20, 30, 40) # Output: The sum of (10, 20, 30, 40) is: 100
sum_all_numbers() # Output: The sum of () is: 0
print("-" * 30)
# Example 2: Using **kwargs to print user preferences
# `**preferences` will collect all keyword arguments into a dictionary.
def display_preferences(**preferences):
print("User Preferences:")
for key, value in preferences.items():
print(f" {key}: {value}")
display_preferences(theme="dark", font_size="medium", notifications=True)
# Output:
# User Preferences:
# theme: dark
# font_size: medium
# notifications: True
display_preferences(language="English", timezone="PST")
# Output:
# User Preferences:
# language: English
# timezone: PST
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Combining normal arguments with *args
# Normal arguments come first, then *args.
def describe_items(category, *items):
print(f"Category: {category}")
if items:
print("Items:")
for item in items:
print(f"- {item}")
else:
print("No items specified.")
describe_items("Fruits", "apple", "banana", "cherry")
# Output:
# Category: Fruits
# Items:
# - apple
# - banana
# - cherry
describe_items("Vegetables")
# Output:
# Category: Vegetables
# No items specified.
print("-" * 30)
# Example 4: Combining normal arguments with **kwargs
# Normal arguments come first, then **kwargs.
def create_configuration(name, version="1.0", **settings):
print(f"Configuration Name: {name}, Version: {version}")
print("Additional Settings:")
for key, value in settings.items():
print(f" {key}: {value}")
create_configuration("WebServer", port=80, enable_ssl=True)
# Output:
# Configuration Name: WebServer, Version: 1.0
# Additional Settings:
# port: 80
# enable_ssl: True
create_configuration("Database", version="2.5", timeout=300, max_connections=100)
# Output:
# Configuration Name: Database, Version: 2.5
# Additional Settings:
# timeout: 300
# max_connections: 100
print("-" * 30)
Advanced Examples:
Python
# Example 5: Using *args and **kwargs together in a function
# The order is: normal args, *args, default args, **kwargs.
def process_data(action, *values, **options):
print(f"Action: {action}")
if values:
print(f"Processing values: {values}")
if options:
print("With options:")
for key, value in options.items():
print(f" {key}: {value}")
process_data("Log", 10, 20, "message", level="info", timestamp="now")
# Output:
# Action: Log
# Processing values: (10, 20, 'message')
# With options:
# level: info
# timestamp: now
process_data("Save", file_name="data.txt", mode="append")
# Output:
# Action: Save
# With options:
# file_name: data.txt
# mode: append
print("-" * 30)
# Example 6: Function acting as a decorator factory (advanced use of *args, **kwargs)
# Demonstrates how *args and **kwargs enable building highly flexible tools.
def my_decorator_factory(prefix=""):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"{prefix}Before calling {func.__name__}...")
result = func(*args, **kwargs)
print(f"{prefix}After calling {func.__name__}. Result: {result}")
return result
return wrapper
return decorator
@my_decorator_factory(prefix="LOG: ")
def calculate_product(x, y, debug=False):
if debug:
print(f"Debugging: Calculating product of {x} and {y}")
return x * y
product_result = calculate_product(5, 4, debug=True)
# Output:
# LOG: Before calling calculate_product...
# Debugging: Calculating product of 5 and 4
# LOG: After calling calculate_product. Result: 20
print("-" * 30)
# Example 7: Unpacking sequences and dictionaries into function arguments
# This is the opposite of *args and **kwargs in function definitions.
# It's powerful for dynamic function calls.
def display_profile(name, age, city, occupation="Unknown"):
print(f"Name: {name}, Age: {age}, City: {city}, Occupation: {occupation}")
# Unpacking a list/tuple into positional arguments
person_data = ["Alice", 30, "New York"]
display_profile(*person_data) # Equivalent to display_profile("Alice", 30, "New York")
# Output: Name: Alice, Age: 30, City: New York, Occupation: Unknown
# Unpacking a dictionary into keyword arguments
profile_data = {"name": "Bob", "age": 25, "city": "London", "occupation": "Developer"}
display_profile(**profile_data) # Equivalent to display_profile(name="Bob", age=25, ...)
# Output: Name: Bob, Age: 25, City: London, Occupation: Developer
# You can combine both:
more_data = ("Charlie", 40)
extra_info = {"city": "Paris", "occupation": "Artist"}
display_profile(*more_data, **extra_info)
# Output: Name: Charlie, Age: 40, City: Paris, Occupation: Artist
print("-" * 30)
Scope of Variables: Where Can Your Variables Be Accessed?
Understanding variable scope is fundamental to writing correct and predictable Python code. Scope refers to the region of your program where a variable is accessible.
Local Scope: Variables Within Functions
Variables defined inside a function have local scope. They can only be accessed from within that function. Once the function finishes execution, these local variables are destroyed.
Note: Local variables Python, function scope, variable accessibility, Python def
scope.
Beginner-Friendly Examples:
Python
# Example 1: Basic local variable
# 'message' is created and used only within 'my_function'.
def my_function():
message = "I am a local variable."
print(message)
my_function() # Output: I am a local variable.
# Trying to access 'message' outside the function will cause an error.
# print(message) # This would raise a NameError: name 'message' is not defined
print("-" * 30)
# Example 2: Local variable in a calculator function
# 'result' is local to 'add'.
def add(a, b):
result = a + b # 'result' is a local variable
print(f"Inside function, result is: {result}")
return result
sum_val = add(10, 5)
print(f"Outside function, sum_val is: {sum_val}") # Output: Outside function, sum_val is: 15
# print(result) # NameError: name 'result' is not defined
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Variables in different local scopes are independent
# 'x' inside 'func1' is different from 'x' inside 'func2'.
def func1():
x = 10 # local to func1
print(f"Inside func1, x is: {x}")
def func2():
x = 20 # local to func2
print(f"Inside func2, x is: {x}")
func1() # Output: Inside func1, x is: 10
func2() # Output: Inside func2, x is: 20
print("-" * 30)
# Example 4: Parameters are also local variables
# 'name' is a local variable within 'greet'.
def greet(name):
city = "London" # 'city' is also local
print(f"Hello, {name} from {city}!")
greet("David") # Output: Hello, David from London!
# print(name) # NameError
# print(city) # NameError
print("-" * 30)
Advanced Examples:
Python
# Example 5: Local variables in nested functions (closure concept)
# Inner functions can access variables from their enclosing (local) scope.
def outer_function(text):
outer_variable = "This is from outer." # outer_variable is local to outer_function
def inner_function():
# inner_function can access outer_variable because it's in its enclosing scope
print(f"{outer_variable} And: {text}")
inner_variable = "This is from inner." # inner_variable is local to inner_function
print(inner_variable)
inner_function()
# print(inner_variable) # NameError: inner_variable is not defined in outer_function's scope
outer_function("Hello from parameter")
# Output:
# This is from outer. And: Hello from parameter
# This is from inner.
print("-" * 30)
# Example 6: Local variables and their lifecycle
# Demonstrates that local variables are created and destroyed with each function call.
def counter():
count = 0 # 'count' is reset to 0 every time 'counter' is called
count += 1
print(f"Current count: {count}")
counter() # Output: Current count: 1
counter() # Output: Current count: 1 (not 2, because 'count' is local)
print("-" * 30)
# Example 7: Using local variables to prevent unintended side effects
# Ensures that operations within a function don't accidentally modify global state.
global_list = [1, 2, 3]
def process_list_locally(input_list):
# Create a local copy to avoid modifying the original global list
local_copy = list(input_list)
local_copy.append(4)
print(f"Inside function, local_copy: {local_copy}")
print(f"Before function call, global_list: {global_list}")
process_list_locally(global_list)
print(f"After function call, global_list: {global_list}") # global_list remains unchanged
# Output:
# Before function call, global_list: [1, 2, 3]
# Inside function, local_copy: [1, 2, 3, 4]
# After function call, global_list: [1, 2, 3]
print("-" * 30)
Global Scope: Variables Accessible Everywhere
Variables defined at the top level of a Python script (outside of any function) have global scope. They can be accessed from anywhere in your code, including inside functions.
Note: Global variables Python, script scope, access variables anywhere, Python global data.
Beginner-Friendly Examples:
Python
# Example 1: Accessing a global variable from inside a function
global_message = "I am a global message." # Defined in global scope
def read_global():
print(global_message) # Can access global_message
read_global() # Output: I am a global message.
print(global_message) # Can access from global scope too
print("-" * 30)
# Example 2: Global variable used in a calculation
PI = 3.14159 # A common global constant
def calculate_circle_area(radius):
area = PI * (radius ** 2)
return area
area_of_circle = calculate_circle_area(5)
print(f"Area of circle with radius 5: {area_of_circle:.2f}") # Output: Area of circle with radius 5: 78.54
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Understanding that a function creates its own local variable if assigned
# If you assign to a variable name that also exists globally, Python creates a new local variable.
global_count = 0
def increment_count():
# This creates a NEW local 'global_count', it does not modify the global one.
global_count = 1 # This is a local variable
print(f"Inside function (local): {global_count}")
increment_count() # Output: Inside function (local): 1
print(f"Outside function (global): {global_count}") # Output: Outside function (global): 0
print("-" * 30)
# Example 4: Accessing a global variable for reading within a loop
# You can read global variables freely without the `global` keyword.
data_source = [1, 2, 3, 4, 5]
def process_data_items():
processed_items = []
for item in data_source: # Reading the global 'data_source'
processed_items.append(item * 2)
print(f"Processed items: {processed_items}")
process_data_items() # Output: Processed items: [2, 4, 6, 8, 10]
print("-" * 30)
Advanced Examples:
Python
# Example 5: Using global variables for application state
# While possible, often better to pass state explicitly or use classes.
app_config = {
"DEBUG_MODE": False,
"LOG_LEVEL": "INFO"
}
def set_debug_mode(is_debug):
# This would create a local app_config if you directly assigned to it.
# To modify the global dictionary, you access its elements.
app_config["DEBUG_MODE"] = is_debug
print(f"Debug mode set to: {app_config['DEBUG_MODE']}")
def get_log_level():
return app_config["LOG_LEVEL"]
print(f"Initial Debug Mode: {app_config['DEBUG_MODE']}")
set_debug_mode(True)
print(f"Current Debug Mode: {app_config['DEBUG_MODE']}") # Output: Current Debug Mode: True
print(f"Current Log Level: {get_log_level()}") # Output: Current Log Level: INFO
print("-" * 30)
# Example 6: Global variables in modules (effectively global to the module)
# When imported, these behave like global variables within that module's context.
# (This example is conceptual, as it requires a separate file)
# imagine 'config_module.py'
# SOME_CONSTANT = 100
# def get_constant():
# return SOME_CONSTANT
#
# in main.py:
# import config_module
# print(config_module.SOME_CONSTANT)
# print(config_module.get_constant())
print("Conceptual: Global variables within modules behave as global to that module.")
print("-" * 30)
# Example 7: Caching results using a global dictionary
# A common pattern where global state is acceptable for performance.
cache = {}
def expensive_calculation(n):
if n in cache:
print(f"Returning from cache for {n}")
return cache[n]
print(f"Calculating for {n}...")
result = n * n * n # Simulate expensive calculation
cache[n] = result
return result
print(expensive_calculation(5)) # Calculates, then stores
print(expensive_calculation(10)) # Calculates, then stores
print(expensive_calculation(5)) # Retrieves from cache
# Output:
# Calculating for 5...
# 125
# Calculating for 10...
# 1000
# Returning from cache for 5
# 125
print("-" * 30)
The global
Keyword: Modifying Global Variables
If you need to modify a global variable from inside a function, you must explicitly declare it using the global
keyword. Without global
, an assignment to a variable name that already exists globally will create a new local variable with the same name.
Note: global
keyword Python, modify global variable, Python function write to global, global scope modification.
Beginner-Friendly Examples:
Python
# Example 1: Modifying a global counter
counter = 0 # Global variable
def increment_global_counter():
global counter # Declare intent to modify the global 'counter'
counter += 1
print(f"Inside function, counter is: {counter}")
print(f"Initial global counter: {counter}") # Output: Initial global counter: 0
increment_global_counter() # Output: Inside function, counter is: 1
increment_global_counter() # Output: Inside function, counter is: 2
print(f"Final global counter: {counter}") # Output: Final global counter: 2
print("-" * 30)
# Example 2: Changing a global status message
status_message = "Application is idle."
def set_status(new_status):
global status_message
status_message = new_status
print(f"Status updated to: '{status_message}'")
print(f"Current status: '{status_message}'")
set_status("Application is running.") # Output: Status updated to: 'Application is running.'
print(f"Current status: '{status_message}'") # Output: Current status: 'Application is running.'
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Using `global` to track function calls
call_count = 0
def log_function_call(function_name):
global call_count
call_count += 1
print(f"Function '{function_name}' called. Total calls: {call_count}")
log_function_call("start_app")
log_function_call("load_data")
log_function_call("display_ui")
print("-" * 30)
# Example 4: Updating a global configuration setting
app_settings = {"theme": "light", "language": "en"}
def update_setting(key, value):
global app_settings
if key in app_settings:
app_settings[key] = value
print(f"Setting '{key}' updated to '{value}'.")
else:
print(f"Error: Setting '{key}' not found.")
print(f"Initial settings: {app_settings}")
update_setting("theme", "dark")
update_setting("language", "es")
update_setting("version", "1.0") # Error: Setting 'version' not found.
print(f"Updated settings: {app_settings}")
# Output:
# Initial settings: {'theme': 'light', 'language': 'en'}
# Setting 'theme' updated to 'dark'.
# Setting 'language' updated to 'es'.
# Error: Setting 'version' not found.
# Updated settings: {'theme': 'dark', 'language': 'es'}
print("-" * 30)
Advanced Examples:
Python
# Example 5: `global` with mutable objects (like lists)
# You don't need `global` to modify the *contents* of a mutable global object,
# but you *do* need it to reassign the object itself.
global_list_data = [1, 2, 3]
def append_to_global_list(item):
# No 'global' needed here because we are modifying the list in place
global_list_data.append(item)
print(f"List after append: {global_list_data}")
def replace_global_list(new_list):
global global_list_data # 'global' is needed to reassign the variable itself
global_list_data = new_list
print(f"List after replacement: {global_list_data}")
print(f"Initial list: {global_list_data}")
append_to_global_list(4) # Output: List after append: [1, 2, 3, 4]
append_to_global_list(5) # Output: List after append: [1, 2, 3, 4, 5]
replace_global_list([10, 20]) # Output: List after replacement: [10, 20]
print(f"Final list: {global_list_data}") # Output: Final list: [10, 20]
print("-" * 30)
# Example 6: Using `global` for a singleton pattern (less common in modern Python)
# While classes are usually preferred for singletons, this demonstrates `global`.
_instance = None # Global variable to hold the singleton instance
def get_singleton_instance():
global _instance
if _instance is None:
print("Creating new instance...")
_instance = "My Singleton Object" # Replace with actual object creation
else:
print("Returning existing instance...")
return _instance
inst1 = get_singleton_instance() # Output: Creating new instance...
inst2 = get_singleton_instance() # Output: Returning existing instance...
print(f"Are instances the same? {inst1 is inst2}") # Output: Are instances the same? True
print("-" * 30)
# Example 7: Conditional modification of a global flag
is_active = False
def activate_system():
global is_active
if not is_active:
is_active = True
print("System activated.")
else:
print("System already active.")
def deactivate_system():
global is_active
if is_active:
is_active = False
print("System deactivated.")
else:
print("System already inactive.")
activate_system() # Output: System activated.
activate_system() # Output: System already active.
deactivate_system() # Output: System deactivated.
deactivate_system() # Output: System already inactive.
print("-" * 30)
The nonlocal
Keyword: Modifying Enclosing Scope Variables
The nonlocal
keyword is used in nested functions. It allows an inner function to modify a variable in its immediately enclosing (non-global) scope. Without nonlocal
, attempting to assign to such a variable would create a new local variable within the inner function.
Note: nonlocal
keyword Python, nested function scope, modify enclosing variable, Python closure scope.
Beginner-Friendly Examples:
Python
# Example 1: Basic use of `nonlocal` with a counter
def outer_function():
count = 0 # This is in the outer (enclosing) scope for inner_function
def inner_function():
nonlocal count # Declare intent to modify 'count' from outer_function's scope
count += 1
print(f"Inner count: {count}")
inner_function() # Output: Inner count: 1
inner_function() # Output: Inner count: 2
print(f"Outer count after inner calls: {count}") # Output: Outer count after inner calls: 2
outer_function()
print("-" * 30)
# Example 2: `nonlocal` with a string variable
def create_greeter(initial_phrase):
current_phrase = initial_phrase # Variable in enclosing scope
def greet_with_update(name):
nonlocal current_phrase
print(f"{current_phrase}, {name}!")
current_phrase = "Updated greeting" # Modify enclosing scope variable
return greet_with_update
greeter = create_greeter("Hello")
greeter("Alice") # Output: Hello, Alice!
greeter("Bob") # Output: Updated greeting, Bob!
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Building a simple closure with `nonlocal` for mutable state
# The inner function "remembers" and modifies the outer variable.
def make_accumulator():
total = 0 # Enclosing scope variable
def add_to_total(amount):
nonlocal total
total += amount
print(f"Added {amount}, new total: {total}")
return total
return add_to_total
acc1 = make_accumulator()
acc1(5) # Output: Added 5, new total: 5
acc1(10) # Output: Added 10, new total: 15
acc2 = make_accumulator() # Create a new accumulator with its own 'total'
acc2(20) # Output: Added 20, new total: 20
print("-" * 30)
# Example 4: Using `nonlocal` for flags or states within nested functions
def task_manager():
task_running = False # Enclosing scope flag
def start_task():
nonlocal task_running
if not task_running:
task_running = True
print("Task started.")
else:
print("Task already running.")
def stop_task():
nonlocal task_running
if task_running:
task_running = False
print("Task stopped.")
else:
print("No task running.")
return start_task, stop_task
start, stop = task_manager() # Get the two inner functions
start() # Output: Task started.
start() # Output: Task already running.
stop() # Output: Task stopped.
stop() # Output: No task running.
print("-" * 30)
Advanced Examples:
Python
# Example 5: `nonlocal` in a more complex nested structure for a counter with reset
def create_advanced_counter():
count = 0 # Enclosing scope for 'increment' and 'reset'
def increment(step=1):
nonlocal count
count += step
return count
def reset():
nonlocal count
count = 0
return count
def get_current():
return count
return increment, reset, get_current
inc, reset_counter, get_count = create_advanced_counter()
print(f"Current: {get_count()}") # Output: Current: 0
print(f"Increment by 1: {inc()}") # Output: Increment by 1: 1
print(f"Increment by 5: {inc(5)}") # Output: Increment by 5: 6
print(f"Current: {get_count()}") # Output: Current: 6
print(f"Reset: {reset_counter()}") # Output: Reset: 0
print(f"Current: {get_count()}") # Output: Current: 0
print("-" * 30)
# Example 6: Implementing a memoization cache using `nonlocal`
# Optimizing recursive functions by storing results in an enclosing cache.
def memoize(func):
cache = {} # This will be the nonlocal variable for the wrapper
def wrapper(*args):
nonlocal cache # Declare intent to modify the 'cache' from memoize's scope
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(f"Fibonacci(10): {fibonacci(10)}")
print(f"Fibonacci(5): {fibonacci(5)}") # Will use cached values for sub-problems
# You won't see repeated calculations for fib(8), fib(7), etc. if fib(10) was called first.
print("-" * 30)
# Example 7: Using `nonlocal` to manage state in a generator factory
def id_generator_factory(start_id=1):
current_id = start_id # This variable is in the enclosing scope
def generate_id():
nonlocal current_id
new_id = current_id
current_id += 1 # Modify the enclosing scope variable
return new_id
return generate_id
id_gen1 = id_generator_factory(100)
print(f"Generated ID 1: {id_gen1()}") # Output: Generated ID 1: 100
print(f"Generated ID 2: {id_gen1()}") # Output: Generated ID 2: 101
id_gen2 = id_generator_factory(1)
print(f"Generated ID (new gen): {id_gen2()}") # Output: Generated ID (new gen): 1
print("-" * 30)
Lambda Functions (Anonymous Functions): Quick, Inline Functions
Lambda functions are small, anonymous functions defined with the lambda
keyword. They can have any number of arguments but can only have one expression. The result of this expression is implicitly returned. They are often used for short, throwaway functions where a full def
statement would be overkill.
Note: Lambda functions Python, anonymous functions, lambda
keyword, inline functions, Python functional programming.
Syntax and Use Cases
Syntax: lambda arguments: expression
Use Cases: Primarily for short, simple operations that fit on a single line, especially when passed as arguments to higher-order functions (like map
, filter
, sorted
).
Beginner-Friendly Examples:
Python
# Example 1: Simple lambda function for addition
# No name, just directly assigned or used.
add_lambda = lambda a, b: a + b
print(f"Lambda addition (2 + 3): {add_lambda(2, 3)}") # Output: Lambda addition (2 + 3): 5
print("-" * 30)
# Example 2: Lambda for squaring a number
square_lambda = lambda x: x * x
print(f"Lambda square of 7: {square_lambda(7)}") # Output: Lambda square of 7: 49
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Using lambda with `sort()` for custom sorting
# Sorting a list of tuples by the second element.
students = [('Alice', 85), ('Bob', 92), ('Charlie', 78)]
students.sort(key=lambda student: student[1]) # Sorts by score (second element)
print(f"Sorted students by score: {students}")
# Output: Sorted students by score: [('Charlie', 78), ('Alice', 85), ('Bob', 92)]
print("-" * 30)
# Example 4: Using lambda with `filter()`
# Filtering out even numbers from a list.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
odd_numbers = list(filter(lambda x: x % 2 != 0, numbers))
print(f"Odd numbers: {odd_numbers}") # Output: Odd numbers: [1, 3, 5, 7, 9]
print("-" * 30)
Advanced Examples:
Python
# Example 5: Using lambda with `map()` for transformations
# Converting temperatures from Celsius to Fahrenheit.
celsius_temps = [0, 10, 20, 30, 40]
fahrenheit_temps = list(map(lambda c: (c * 9/5) + 32, celsius_temps))
print(f"Celsius to Fahrenheit: {fahrenheit_temps}")
# Output: Celsius to Fahrenheit: [32.0, 50.0, 68.0, 86.0, 104.0]
print("-" * 30)
# Example 6: Lambda function for conditional logic (ternary operator)
# Although lambdas are single-expression, they can use the ternary operator.
check_pass_fail = lambda score: "Pass" if score >= 50 else "Fail"
print(f"Score 65: {check_pass_fail(65)}") # Output: Score 65: Pass
print(f"Score 45: {check_pass_fail(45)}") # Output: Score 45: Fail
print("-" * 30)
# Example 7: Lambda as a return value from a function (closure with lambda)
# This is a powerful advanced use case.
def make_power_calculator(power):
return lambda base: base ** power # Returns a lambda function
square_calculator = make_power_calculator(2)
cube_calculator = make_power_calculator(3)
print(f"Using square_calculator(6): {square_calculator(6)}") # Output: Using square_calculator(6): 36
print(f"Using cube_calculator(4): {cube_calculator(4)}") # Output: Using cube_calculator(4): 64
print("-" * 30)
Higher-Order Functions: Functions That Operate on Other Functions
Higher-order functions are functions that either take one or more functions as arguments or return a function as their result. They are a core concept in functional programming and enable powerful, concise code.
Note: Higher-order functions Python, functional programming, map
filter reduce
Python, passing functions as arguments.
map()
: Applying a Function to Each Item
The map()
function applies a given function to each item in an iterable (like a list) and returns an iterator of the results.
Syntax: map(function, iterable)
Note: Python map
function, apply function to list, transform elements.
Beginner-Friendly Examples:
Python
# Example 1: Squaring each number in a list using map and a regular function
def square(x):
return x * x
numbers = [1, 2, 3, 4, 5]
squared_numbers_map = list(map(square, numbers)) # Convert map object to list
print(f"Numbers squared (using map and def): {squared_numbers_map}")
# Output: Numbers squared (using map and def): [1, 4, 9, 16, 25]
print("-" * 30)
# Example 2: Converting strings to uppercase using map and a lambda
words = ["hello", "world", "python"]
uppercase_words = list(map(lambda word: word.upper(), words))
print(f"Uppercase words (using map and lambda): {uppercase_words}")
# Output: Uppercase words (using map and lambda): ['HELLO', 'WORLD', 'PYTHON']
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Applying multiple arguments to a function with map (using lambda)
# Note: map works with a single iterable; for multiple, use a lambda with multiple args.
num1 = [1, 2, 3]
num2 = [4, 5, 6]
sums = list(map(lambda x, y: x + y, num1, num2))
print(f"Sums of corresponding elements: {sums}") # Output: Sums of corresponding elements: [5, 7, 9]
print("-" * 30)
# Example 4: Formatting prices using map
prices = [10.5, 20.0, 5.75]
formatted_prices = list(map(lambda p: f"${p:.2f}", prices))
print(f"Formatted prices: {formatted_prices}") # Output: Formatted prices: ['$10.50', '$20.00', '$5.75']
print("-" * 30)
Advanced Examples:
Python
# Example 5: Using map with a function from another module
# Example using `math.sqrt` with map.
import math
values = [1, 4, 9, 16, 25]
sqrt_values = list(map(math.sqrt, values))
print(f"Square roots: {sqrt_values}") # Output: Square roots: [1.0, 2.0, 3.0, 4.0, 5.0]
print("-" * 30)
# Example 6: Map to extract specific data from a list of dictionaries
users = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 24},
{"name": "Charlie", "age": 35}
]
user_names = list(map(lambda user: user["name"], users))
user_ages = list(map(lambda user: user["age"], users))
print(f"User names: {user_names}") # Output: User names: ['Alice', 'Bob', 'Charlie']
print(f"User ages: {user_ages}") # Output: User ages: [30, 24, 35]
print("-" * 30)
# Example 7: Using map with a complex function and multiple inputs (using `zip`)
# Combining names and scores into a formatted string.
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
results = list(map(lambda n, s: f"{n} scored {s} points.", names, scores))
print(f"Detailed scores: {results}")
# Output: Detailed scores: ['Alice scored 85 points.', 'Bob scored 92 points.', 'Charlie scored 78 points.']
print("-" * 30)
filter()
: Selecting Items Based on a Condition
The filter()
function constructs an iterator from elements of an iterable for which a function returns true.
Syntax: filter(function, iterable)
Note: Python filter
function, select elements from list, conditional filtering.
Beginner-Friendly Examples:
Python
# Example 1: Filtering even numbers using a regular function
def is_even(num):
return num % 2 == 0
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers_filter = list(filter(is_even, numbers))
print(f"Even numbers (using filter and def): {even_numbers_filter}")
# Output: Even numbers (using filter and def): [2, 4, 6, 8, 10]
print("-" * 30)
# Example 2: Filtering names starting with 'A' using a lambda
names = ["Alice", "Bob", "Anna", "Charlie", "Adam"]
a_names = list(filter(lambda name: name.startswith('A'), names))
print(f"Names starting with 'A': {a_names}")
# Output: Names starting with 'A': ['Alice', 'Anna', 'Adam']
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Filtering positive numbers from a list
values = [-2, -1, 0, 1, 2, 3, -5]
positive_values = list(filter(lambda x: x > 0, values))
print(f"Positive values: {positive_values}") # Output: Positive values: [1, 2, 3]
print("-" * 30)
# Example 4: Filtering strings longer than a certain length
words = ["apple", "banana", "cat", "dog", "elephant"]
long_words = list(filter(lambda word: len(word) > 4, words))
print(f"Words longer than 4 characters: {long_words}")
# Output: Words longer than 4 characters: ['apple', 'banana', 'elephant']
print("-" * 30)
Advanced Examples:
Python
# Example 5: Filtering based on multiple conditions using 'and'/'or' in lambda
products = [
{"name": "Laptop", "price": 1200, "in_stock": True},
{"name": "Mouse", "price": 25, "in_stock": True},
{"name": "Keyboard", "price": 75, "in_stock": False},
{"name": "Monitor", "price": 300, "in_stock": True}
]
# Filter products that are in stock AND price is less than 500
affordable_in_stock = list(filter(lambda p: p["in_stock"] and p["price"] < 500, products))
print(f"Affordable and in stock: {affordable_in_stock}")
# Output: Affordable and in stock: [{'name': 'Mouse', 'price': 25, 'in_stock': True}, {'name': 'Monitor', 'price': 300, 'in_stock': True}]
print("-" * 30)
# Example 6: Filtering out None values from a list
data = [1, None, 2, "hello", None, 3, []]
cleaned_data = list(filter(lambda x: x is not None, data))
print(f"Data with None removed: {cleaned_data}")
# Output: Data with None removed: [1, 2, 'hello', 3, []]
print("-" * 30)
# Example 7: Using `filter` with a more complex function that checks divisibility
def is_divisible_by_3_or_5(num):
return num % 3 == 0 or num % 5 == 0
numbers = range(1, 21) # Numbers from 1 to 20
divisible_numbers = list(filter(is_divisible_by_3_or_5, numbers))
print(f"Numbers divisible by 3 or 5 (1-20): {divisible_numbers}")
# Output: Numbers divisible by 3 or 5 (1-20): [3, 5, 6, 9, 10, 12, 15, 18, 20]
print("-" * 30)
reduce()
(from functools
): Aggregating a Sequence
The reduce()
function applies a function of two arguments cumulatively to the items of an iterable, from left to right, to reduce the iterable to a single value. It's part of the functools
module, so you need to import it.
Syntax: reduce(function, iterable[, initializer])
Note: Python reduce
function, aggregate list elements, cumulative operation, functools
module.
Beginner-Friendly Examples:
Python
# Example 1: Summing all numbers in a list using reduce
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# reduce(lambda x, y: x + y, [1, 2, 3, 4, 5])
# 1 + 2 = 3
# 3 + 3 = 6
# 6 + 4 = 10
# 10 + 5 = 15
sum_result = reduce(lambda x, y: x + y, numbers)
print(f"Sum using reduce: {sum_result}") # Output: Sum using reduce: 15
print("-" * 30)
# Example 2: Finding the maximum number in a list using reduce
max_num = reduce(lambda x, y: x if x > y else y, numbers)
print(f"Max number using reduce: {max_num}") # Output: Max number using reduce: 5
print("-" * 30)
Intermediate Examples:
Python
# Example 3: Concatenating strings in a list
words = ["Python", "is", "awesome"]
sentence = reduce(lambda x, y: x + " " + y, words)
print(f"Concatenated sentence: '{sentence}'") # Output: Concatenated sentence: 'Python is awesome'
print("-" * 30)
# Example 4: Calculating factorial using reduce
# 5! = 5 * 4 * 3 * 2 * 1
factorial_num = 5
factorial_result = reduce(lambda x, y: x * y, range(1, factorial_num + 1))
print(f"Factorial of {factorial_num} using reduce: {factorial_result}") # Output: Factorial of 5 using reduce: 120
print("-" * 30)
Advanced Examples:
Python
# Example 5: Using reduce with an initializer
# The initializer is the starting value for the aggregation.
# This is useful when the iterable might be empty or you need a specific starting point.
data_list = [10, 20, 30]
initial_value = 100
sum_with_initial = reduce(lambda x, y: x + y, data_list, initial_value)
print(f"Sum with initial value {initial_value}: {sum_with_initial}") # Output: Sum with initial value 100: 160
empty_list = []
sum_empty_list_with_initial = reduce(lambda x, y: x + y, empty_list, 0)
print(f"Sum of empty list with initial 0: {sum_empty_list_with_initial}") # Output: Sum of empty list with initial 0: 0
# If no initializer is given for an empty list, reduce raises a TypeError.
print("-" * 30)
# Example 6: Flattening a list of lists using reduce
list_of_lists = [[1, 2], [3, 4, 5], [6]]
flattened_list = reduce(lambda acc, sublist: acc + sublist, list_of_lists, [])
print(f"Flattened list: {flattened_list}") # Output: Flattened list: [1, 2, 3, 4, 5, 6]
print("-" * 30)
# Example 7: Building a dictionary from a list of key-value pairs using reduce
items = [("apple", 1), ("banana", 2), ("orange", 3)]
item_dict = reduce(lambda acc, item: {**acc, item[0]: item[1]}, items, {})
print(f"Dictionary from items: {item_dict}")
# Output: Dictionary from items: {'apple': 1, 'banana': 2, 'orange': 3}
print("-" * 30)
You've completed Module 5 on Functions! You've learned about defining functions, passing arguments, returning values, understanding variable scope, and utilizing powerful higher-order functions like map
, filter
, and reduce
. Keep practicing these concepts to build robust and efficient Python applications!