# -*- coding: utf-8 -*-
"""
================================================================================
Comprehensive Python Guide: User-Defined Functions and Scope
================================================================================
This program provides an in-depth demonstration of how to create and use
functions in Python. It also explains the concept of variable scope, which
determines the accessibility of variables in different parts of your code.
Sections:
1. Defining and Calling Functions
2. Function Arguments (Positional, Keyword, Default, *args, **kwargs)
3. Return Values
4. Variable Scope (Local, Global, Nonlocal)
5. Lambda (Anonymous) Functions
"""
# A function to print section headers for better readability.
def print_header(title):
"""Prints a formatted header to the console."""
print("\n" + "="*60)
print(f"| {title.center(56)} |")
print("="*60)
# ==============================================================================
# SECTION 1: DEFINING AND CALLING FUNCTIONS
# ==============================================================================
print_header("Defining and Calling Functions")
# --- 1.1: A simple function with no arguments ---
def greet():
"""This function prints a simple greeting."""
print("Hello! Welcome to the world of Python functions.")
print("Calling the 'greet' function:")
greet()
# --- 1.2: A function with required positional arguments ---
def greet_user(name, message):
"""This function greets a user with a specific message."""
print(f"Hello, {name}. {message}")
print("\nCalling 'greet_user' with positional arguments:")
greet_user("Alice", "Have a great day!")
# ==============================================================================
# SECTION 2: FUNCTION ARGUMENTS
# ==============================================================================
print_header("Function Arguments")
# --- 2.1: Keyword Arguments ---
# You can specify arguments by their name, making the order irrelevant.
print("\n--- 2.1: Keyword Arguments ---")
print("Calling 'greet_user' with keyword arguments (order changed):")
greet_user(message="Hope you are well!", name="Bob")
# --- 2.2: Default Arguments ---
# Arguments can have default values if none are provided in the call.
def create_user(username, is_admin=False, theme="light"):
"""Creates a user with default settings."""
print(f"Creating user '{username}'...")
print(f" - Admin status: {is_admin}")
print(f" - Theme: {theme}")
print("\n--- 2.2: Default Arguments ---")
print("Calling 'create_user' with only the required argument:")
create_user("charlie")
print("\nCalling 'create_user' and overriding a default argument:")
create_user("diana", is_admin=True)
# --- 2.3: Arbitrary Positional Arguments (*args) ---
# Allows a function to accept any number of positional arguments.
# They are collected into a tuple.
def calculate_sum(*numbers):
"""Calculates the sum of an arbitrary number of values."""
print(f"Arguments received as a tuple: {numbers}")
total = sum(numbers)
print(f"The sum is: {total}")
print("\n--- 2.3: Arbitrary Positional Arguments (*args) ---")
calculate_sum(1, 2, 3)
calculate_sum(10, 20, 30, 40, 50)
# --- 2.4: Arbitrary Keyword Arguments (**kwargs) ---
# Allows a function to accept any number of keyword arguments.
# They are collected into a dictionary.
def display_user_profile(**details):
"""Displays user profile information from keyword arguments."""
print(f"Arguments received as a dictionary: {details}")
for key, value in details.items():
print(f" - {key.replace('_', ' ').title()}: {value}")
print("\n--- 2.4: Arbitrary Keyword Arguments (**kwargs) ---")
display_user_profile(name="Eve", age=28, city="London", occupation="Engineer")
# ==============================================================================
# SECTION 3: RETURN VALUES
# ==============================================================================
print_header("Return Values")
# --- 3.1: Returning a single value ---
def square(number):
"""Returns the square of a number."""
return number * number
print("\n--- 3.1: Returning a single value ---")
result = square(5)
print(f"The result of square(5) is: {result}")
# --- 3.2: Returning multiple values ---
# Python functions can return multiple values, which are packed into a tuple.
def get_user_stats(user_id):
"""A mock function that returns multiple stats for a user."""
# In a real app, you might fetch this from a database.
name = "Frank"
posts = 42
followers = 1500
return name, posts, followers
print("\n--- 3.2: Returning multiple values ---")
user_info = get_user_stats(101)
print(f"The returned value is a tuple: {user_info}")
# You can unpack the tuple directly into variables
user_name, num_posts, num_followers = user_info
print(f"Unpacked values: Name={user_name}, Posts={num_posts}, Followers={num_followers}")
# ==============================================================================
# SECTION 4: VARIABLE SCOPE (LEGB Rule: Local, Enclosing, Global, Built-in)
# ==============================================================================
print_header("Variable Scope")
# --- 4.1: Global vs. Local Scope ---
x = "I am a global variable" # This is a global variable
def scope_test():
# This 'x' is local to the function and "shadows" the global one.
x = "I am a local variable"
print(f"Inside the function: {x}")
print("\n--- 4.1: Global vs. Local Scope ---")
print(f"Outside the function: {x}")
scope_test()
print(f"Outside the function (unchanged): {x}")
# --- 4.2: The 'global' keyword ---
# To modify a global variable from inside a function, use the 'global' keyword.
counter = 0
def increment_counter():
global counter # Declare that we want to modify the global 'counter'
counter += 1
print(f"Counter inside function: {counter}")
print("\n--- 4.2: The 'global' keyword ---")
print(f"Counter before calling function: {counter}")
increment_counter()
increment_counter()
print(f"Counter after calling function: {counter}")
# --- 4.3: The 'nonlocal' keyword (Enclosing Scope) ---
# Used in nested functions to modify a variable from an enclosing (but not global) scope.
def outer_function():
message = "I am from the outer function"
def inner_function():
nonlocal message # Modify the variable from the enclosing scope
message = "I was modified by the inner function!"
print(f"Inside inner function: {message}")
print(f"Before inner_function call: {message}")
inner_function()
print(f"After inner_function call: {message}")
print("\n--- 4.3: The 'nonlocal' keyword ---")
outer_function()
# ==============================================================================
# SECTION 5: LAMBDA (ANONYMOUS) FUNCTIONS
# ==============================================================================
# Small, one-line functions defined without a name using the 'lambda' keyword.
print_header("Lambda (Anonymous) Functions")
# --- 5.1: Basic Lambda function ---
# lambda arguments: expression
add = lambda a, b: a + b
print("\n--- 5.1: Basic Lambda function ---")
print(f"Using a lambda function to add 10 and 20: {add(10, 20)}")
# --- 5.2: Practical use case with sorting ---
# Lambdas are often used as arguments to higher-order functions like sorted(), map(), filter().
points = [(1, 5), (9, 2), (4, 7)]
print(f"\n--- 5.2: Practical use case ---")
print(f"Original list of points: {points}")
# Sort the list of tuples based on the second element of each tuple
points_sorted = sorted(points, key=lambda point: point[1])
print(f"List sorted by the second element in each tuple: {points_sorted}")
print("\n" + "="*60)
print("DEMONSTRATION COMPLETE".center(60))
print("="*60)