Python

P
PythonHub
Progress 0%

Python

  • Home
  • History of Python
  • Applications of Python
  • Introduction To Python
    • What is Python
    • Character Set
    • Tokens in Python
    • Python Execution Mode
    • Variable And Identifiers
    • Data Types in Python
    • Operators And Expressions
    • Constants in Python
    • Assignment Statement
    • Input / Output in Python
    • Simple Python Scripts
    • Namespace in Python
    • 📝 Assignments
  • Operators in Python
    • Arithmetic Operators
    • Assignment Operators
    • Shorthand Assignment Operators
    • Relational Operators
    • Logical Operators
    • Bitwise Operators
    • Special Operators
    • 📝 Assignments
  • Input Output in Python
    • Accept Input
    • Output Formatting
    • 📝 Assignments
  • Conditional Statement
    • Decision Making
    • if Statement
    • IF-ELSE STATEMENT
    • IF-ELSE LADDER
    • NESTED IF-ELSE
    • Short Hand IF-ELSE
    • 📝 Assignments
  • Loops
    • Introduction to Loops
    • While Loop
    • Nested While Loop
    • 📝 While Loop Assignments
    • For Loop
    • For Loop Examples
    • Nested For Loop
    • Nested For Loop Examples
    • Infinite While Loops
    • Infinite For Loops
    • Break, Continue and Else in Loops
    • Difference Between For and While Loop
    • For Each Loop
    • 📝 For Each Assignments
    • 📝 All Loops Assignments
  • List
    • List in Python
    • Access List Elements
    • List Functions
    • Iterate (Loop) List
    • List Comprehension
    • 📝 Assignments
  • Tuple
    • Tuple in Python
    • Access Tuple Elements
    • Tuple Functions
    • Iterate (Loop) Tuple
    • Unpack Tuple
    • Tuple Comprehension
    • 📝 Assignments
  • Set
    • Set in Python
    • Access Set Elements
    • Set Methods
    • Iterate (Loop) Set
    • Pack/Unpack Set
    • Set Comprehension
    • 📝 Assignments
  • Dictionary
    • Dictionary
    • Access Dictionary Items
    • Dictionary Methods
    • Iterate (Loop) Dictionary
    • Formatting Dictionaries
    • Nested Dictionaries
    • Dictionary Comprehension
    • 📝 Assignments
  • Diff List Tuple Set Dictionary
    • List vs Tuple
    • List vs Set
    • List vs Dictionary
    • Tuple vs Set
    • Tuple vs Dictionary
    • Dictionary vs Set
    • 📝 Assignments
  • Exception
    • Error vs Exception
    • Types of Exception
    • Exception Handling
    • User Defined Exception
    • Logging Exception
    • 📝 Assignments
  • Functions
    • Introduction to Functions
    • Modular Programming
    • Types of Functions
    • Inbuilt Functions
    • Need For User-Defined Function
    • Elements of User Defined Function
    • Function Arguments
    • Nesting of Functions
    • Recursion
    • Global Local and Non Local
    • Python Lambda Functions
    • 📝 Assignments
  • Python Module
    • Introduction to Module
    • Inbuilt Modules in Python
    • User Defined Module
    • 📝 Assignments
  • File Handling
    • Introduction to Files
    • Create File
    • Read Files
    • Write to File
    • Rename File
    • Copy File
    • Move File
    • List Files in Directory
    • Binary Files
    • Zipping and Unzipping Files
    • 📝 Assignments
  • Strings
    • Basics of Strings
    • String Special Operators
    • String Formatting Operators
    • String Methods
    • 📝 Assignments
  • Regular Expressions
  • Python OOPS
    • Basics of Object Oriented
    • What are Classes and Objects?
    • Creating Class and Object
    • OOP vs Procedural Programming
    • Difference Between Classes and Objects
    • Constructors
    • Destructor
    • Built Class Methods and Attributes
    • Class and Instance Variables
    • Inheritance in Python
    • Single Inheritance
    • Multiple Inheritance
    • Multilevel Inheritance
    • Hierarchical Inheritance
    • Hybrid Inheritance
    • Abstraction
    • Method Overriding
    • Abstract Method
    • Interfaces in Python
    • Abstract Class vs Interface
    • Public, Private and Protected
    • Overloading vs Overriding
    • Inheritance vs Composition
    • Encapsulation
    • Polymorphism
    • Inner Classes
    • 📝 Assignments
  • Advanced Python
    • Iterator in Python
    • Generator in Python
    • Decorator in Python
    • Type Hints in Python
    • Async/Await Programming
    • Dataclasses in Python
    • Context Managers in Python
    • Property Decorator in Python
    • __slots__ in Python
    • Enums in Python
    • Walrus Operator in Python
    • Match-Case in Python
    • 📝 Assignments
  • Python Standard Library
    • Collections Module
    • Itertools Module
    • Functools Module
    • Datetime Module
    • JSON Module
    • OS Module
    • Sys Module
    • Random Module
    • Math Module
    • 📝 Assignments
  • Python Testing
    • Unit Testing in Python
    • Pytest Framework
    • Mocking in Python
    • 📝 Assignments
  • Python Best Practices
    • PEP 8 Style Guide
    • Docstrings in Python
    • Logging in Python
    • Code Optimization Tips
    • Debugging Techniques
    • 📝 Assignments
  • MySQL Database in Python
    • Introduction to MySQL with Python
    • DBMS vs File System
    • Connecting to MySQL Database
    • Create Database in MySQL
    • Create Table in MySQL
    • Insert Data in MySQL
    • Insert Multiple Rows
    • Select Data from MySQL
    • WHERE Clause in MySQL
    • Update Data in MySQL
    • Delete Data from MySQL
    • Parameterized Queries
    • Transaction Management
    • Error Handling
    • Connection Pooling
    • MySQL Drivers Guide
    • Joins in MySQL
    • Aggregation Functions
    • Backup MySQL Database
    • Best Practices
    • 📝 Assignments
  • MySQL Database Operations
    • SELECT Statement
    • MySQL Operators
    • DDL Statements
    • DML Statements
    • Subqueries
    • JOIN Operations
    • Aggregation
    • Case Study
    • 📝 Assignments
  • Graphics in Python
  • Threads in Python
    • Introduction to Threads
    • Process vs Threads
    • Concurrent Programming & GIL
    • Uses of Threads
    • Creating Threads
    • Single Tasking
    • Multi Tasking
    • Thread Synchronization
    • 📝 Assignments
  • Interview Questions & Answers
  • Python Case Studies
  • Multiple Choice Questions
  • 📝 Practice Problems
Get in Touch
  • tech2dsm@gmail.com

© Sankalan Data Tech

Python Language Interactive Tutorial

Python: Nesting of Functions

Python Nesting of Functions - Complete Guide

Learn about nested functions and closures in Python.

Created by Sankalan Data Tech Team Verified
Data Engineers, Analysts, Scientists & Trainers
Created by experienced Python developers, data engineers, and data scientists to make programming easy through practical examples, real-world experience, and clear explanations.
On this page:
  • What are Nested Functions?
  • Basic Nested Functions
  • Scope and Variable Access
  • Closures
  • Use Cases for Nested Functions
  • Nested Functions in Decorators
  • Best Practices
  • Try It Yourself
  • Quiz
  • FAQ
Share this tutorial:
Twitter LinkedIn Facebook WhatsApp Reddit Telegram Email Copy Link
What You'll Learn Here
  • What are nested functions — functions inside functions
  • Scope and variable access — how inner functions access outer variables
  • Closures — functions that remember their environment
  • Use cases — when to use nested functions
  • Decorators — nested functions in decorators
  • Best practices — guidelines for using nested functions

What are Nested Functions?

In Python, a nested function (or inner function) is a function defined inside another function. This allows you to encapsulate functionality and create closures, which are powerful programming patterns.

💡 Key concept: Nested functions are functions defined within the body of another function. They have access to the outer function's variables and can capture their state, creating closures.

Basic Nested Functions

1

Functions Inside Functions

# A nested function is defined inside another function

# 1. Simple nested function
def outer_function():
    print("This is the outer function")
    
    def inner_function():
        print("This is the inner function")
    
    # Call the inner function
    inner_function()

outer_function()
# This is the outer function
# This is the inner function

# 2. Inner function with parameters
def calculate_total(price, quantity):
    """Calculate total with discount"""
    
    def apply_discount(amount, discount_percent):
        """Apply discount to amount"""
        return amount * (1 - discount_percent / 100)
    
    subtotal = price * quantity
    discounted = apply_discount(subtotal, 10)  # 10% discount
    return discounted

print(calculate_total(100, 3))  # 270.0

# 3. Multiple inner functions
def process_data(data):
    """Process data with multiple inner functions"""
    
    def clean_data(d):
        """Clean the data"""
        return [x.strip() for x in d if x]
    
    def transform_data(d):
        """Transform the data"""
        return [x.upper() for x in d]
    
    def validate_data(d):
        """Validate the data"""
        return all(len(x) > 0 for x in d)
    
    cleaned = clean_data(data)
    transformed = transform_data(cleaned)
    is_valid = validate_data(transformed)
    
    return transformed, is_valid

result, valid = process_data(["  hello  ", "world", "", "  python  "])
print(result)  # ['HELLO', 'WORLD', 'PYTHON']
print(valid)   # True

# 4. Inner functions returning values
def get_multiplier(factor):
    """Create a multiplier function"""
    
    def multiplier(x):
        return x * factor
    
    return multiplier

double = get_multiplier(2)
triple = get_multiplier(3)

print(double(5))  # 10
print(triple(5))  # 15

Nested functions key points:

  • Defined inside — inner function is defined within outer function
  • Access to outer — can access outer function's variables
  • Encapsulation — inner functions are hidden from outside
  • Returned — inner functions can be returned to create closures

Quick Check: What is a nested function? (Answer: A function defined inside another function)

Scope and Variable Access

2

Accessing Variables from Outer Functions

# Nested functions have access to variables in the outer function

# 1. Accessing outer variables
def outer_function():
    outer_var = "I'm from outer"
    
    def inner_function():
        print(f"Inner function says: {outer_var}")
    
    inner_function()

outer_function()
# Inner function says: I'm from outer

# 2. Modifying outer variables (use nonlocal)
def counter():
    count = 0
    
    def increment():
        nonlocal count  # Needed to modify outer variable
        count += 1
        return count
    
    return increment

counter_func = counter()
print(counter_func())  # 1
print(counter_func())  # 2
print(counter_func())  # 3

# 3. Reading vs modifying outer variables
def demo():
    x = 10
    
    def read_only():
        print(f"Reading x: {x}")  # ✅ Can read
    
    def modify_without_nonlocal():
        # x = x + 1  # ❌ UnboundLocalError
        pass
    
    def modify_with_nonlocal():
        nonlocal x
        x += 1  # ✅ Can modify with nonlocal
        return x
    
    read_only()
    print(f"After modification: {modify_with_nonlocal()}")  # 11

demo()

# 4. Variable shadowing
def outer_with_shadow():
    value = "outer"
    
    def inner_shadow():
        value = "inner"  # This shadows the outer variable
        print(f"Inner value: {value}")
    
    inner_shadow()
    print(f"Outer value: {value}")  # Still "outer"

outer_with_shadow()
# Inner value: inner
# Outer value: outer

# 5. Accessing outer variables in nested loops
def create_functions():
    functions = []
    
    for i in range(3):
        def func():
            return i  # Captures i by reference
        functions.append(func)
    
    return functions

funcs = create_functions()
print(funcs[0]())  # 2 (not 0!) - i is captured by reference
print(funcs[1]())  # 2
print(funcs[2]())  # 2

# To fix, bind i at definition time
def create_functions_fixed():
    functions = []
    
    for i in range(3):
        def func(i=i):  # Default argument captures current i
            return i
        functions.append(func)
    
    return functions

funcs = create_functions_fixed()
print(funcs[0]())  # 0
print(funcs[1]())  # 1
print(funcs[2]())  # 2

Scope rules for nested functions:

  • Read access — inner functions can read outer variables
  • Modify access — use nonlocal to modify outer variables
  • Shadowing — inner variables can shadow outer variables
  • Late binding — variables are looked up at call time

Quick Check: What keyword allows modifying an outer variable? (Answer: nonlocal)

Closures

3

Functions That Remember Their Environment

# A closure is a nested function that remembers variables from its outer function
# even after the outer function has finished executing

# 1. Basic closure
def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

add_five = outer_function(5)
add_ten = outer_function(10)

print(add_five(3))   # 8
print(add_ten(3))    # 13

# 2. Closure with state
def create_counter():
    count = 0
    
    def counter():
        nonlocal count
        count += 1
        return count
    
    return counter

counter1 = create_counter()
counter2 = create_counter()

print(counter1())  # 1
print(counter1())  # 2
print(counter2())  # 1  ← Each closure has its own state
print(counter2())  # 2

# 3. Closure for configuration
def create_calculator(operation):
    def calculator(a, b):
        if operation == "add":
            return a + b
        elif operation == "subtract":
            return a - b
        elif operation == "multiply":
            return a * b
        elif operation == "divide":
            return a / b
        else:
            return None
    return calculator

add_calc = create_calculator("add")
sub_calc = create_calculator("subtract")
mul_calc = create_calculator("multiply")

print(add_calc(10, 5))   # 15
print(sub_calc(10, 5))   # 5
print(mul_calc(10, 5))   # 50

# 4. Checking closure variables
def outer():
    x = 10
    y = 20
    
    def inner():
        return x + y
    
    return inner

func = outer()
print(func.__closure__)  # Shows the closure cells
print(func.__closure__[0].cell_contents)  # 10
print(func.__closure__[1].cell_contents)  # 20

# 5. Real-world closure - API client
def create_api_client(base_url, api_key):
    """Create an API client with a closure"""
    
    def make_request(endpoint, method="GET", data=None):
        url = f"{base_url}/{endpoint}"
        headers = {"Authorization": f"Bearer {api_key}"}
        return {
            "url": url,
            "method": method,
            "headers": headers,
            "data": data
        }
    
    return make_request

client = create_api_client("https://api.example.com", "abc123")
print(client("users", "GET"))
# {'url': 'https://api.example.com/users', 'method': 'GET', 
#  'headers': {'Authorization': 'Bearer abc123'}, 'data': None}

Closures key points:

  • Remember state — closures preserve variables from the outer function
  • Encapsulate data — create private variables that only the closure can access
  • Each call is independent — each closure has its own state
  • Common use cases — decorators, callbacks, factory functions

Quick Check: What is a closure? (Answer: A nested function that remembers variables from its outer function)

Use Cases for Nested Functions

4

When to Use Nested Functions

# 1. Helper functions that are only needed inside one function
def process_user_data(users):
    """Process user data with helper functions"""
    
    def validate_user(user):
        """Validate a single user"""
        return all(key in user for key in ["name", "age", "email"])
    
    def format_user(user):
        """Format user data"""
        return f"{user['name']} ({user['age']}) - {user['email']}"
    
    valid_users = [user for user in users if validate_user(user)]
    return [format_user(user) for user in valid_users]

users = [
    {"name": "Alice", "age": 25, "email": "alice@email.com"},
    {"name": "Bob", "age": 30},
    {"name": "Charlie", "age": 35, "email": "charlie@email.com"}
]
print(process_user_data(users))
# ['Alice (25) - alice@email.com', 'Charlie (35) - charlie@email.com']

# 2. Factory functions
def create_logger(level):
    """Create a logger with a specific level"""
    
    def log(message):
        print(f"[{level.upper()}] {message}")
    
    return log

error_logger = create_logger("error")
warning_logger = create_logger("warning")

error_logger("Something went wrong!")    # [ERROR] Something went wrong!
warning_logger("Check your input")       # [WARNING] Check your input

# 3. Callback functions
def process_with_callback(data, callback):
    """Process data with a callback"""
    result = data * 2
    
    def handle_result():
        callback(result)
    
    handle_result()

def print_result(x):
    print(f"Result: {x}")

process_with_callback(10, print_result)  # Result: 20

# 4. Function composition
def compose(f, g):
    """Compose two functions: f(g(x))"""
    def composed(x):
        return f(g(x))
    return composed

def add_one(x):
    return x + 1

def multiply_two(x):
    return x * 2

add_then_multiply = compose(multiply_two, add_one)
print(add_then_multiply(3))  # 8 ((3+1)*2)

# 5. Data validation with configuration
def create_validator(rules):
    """Create a validator with specific rules"""
    
    def validate(data):
        for field, rule in rules.items():
            if field in data:
                value = data[field]
                if not rule(value):
                    return False
        return True
    
    return validate

age_rule = lambda x: x >= 18 and x <= 120
email_rule = lambda x: "@" in x and "." in x
name_rule = lambda x: len(x) > 0

validator = create_validator({
    "age": age_rule,
    "email": email_rule,
    "name": name_rule
})

print(validator({"age": 25, "email": "test@email.com", "name": "Alice"}))  # True
print(validator({"age": 15, "email": "test@email.com", "name": "Bob"}))    # False

Common use cases:

  • Helper functions — functions that are only used inside one function
  • Factory functions — creating functions with specific configurations
  • Callbacks — functions that are called after an operation
  • Function composition — combining multiple functions
  • Data validation — creating validators with specific rules

Quick Check: When should you use a helper function as a nested function? (Answer: When it's only used inside the outer function)

Nested Functions in Decorators

5

Decorators Use Nested Functions

# Decorators are implemented using nested functions
# A decorator wraps another function to modify its behavior

# 1. Simple decorator
def timer_decorator(func):
    """Decorator that times how long a function takes"""
    
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.6f} seconds")
        return result
    
    return wrapper

@timer_decorator
def slow_function():
    import time
    time.sleep(0.1)
    return "Done"

slow_function()  # slow_function took 0.100001 seconds

# 2. Decorator with parameters
def retry_decorator(max_retries=3):
    """Decorator that retries a function if it fails"""
    
    def decorator(func):
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_retries:
                        raise
                    print(f"Attempt {attempt + 1} failed. Retrying...")
            return None
        return wrapper
    
    return decorator

@retry_decorator(max_retries=2)
def unstable_function():
    import random
    if random.random() < 0.7:
        raise ValueError("Random failure")
    return "Success"

# Uncomment to test
# print(unstable_function())

# 3. Logging decorator
def log_decorator(func):
    """Decorator that logs function calls"""
    
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result}")
        return result
    
    return wrapper

@log_decorator
def add(a, b):
    return a + b

add(5, 3)
# Calling add with args=(5, 3), kwargs={}
# add returned 8

# 4. Multiple decorators
def uppercase_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

def exclamation_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result + "!!!"
    return wrapper

@uppercase_decorator
@exclamation_decorator
def greet(name):
    return f"Hello, {name}"

print(greet("Alice"))  # HELLO, ALICE!!!

Decorators and nested functions:

  • Wrapper function — the nested function that wraps the original
  • Preserves arguments — *args and **kwargs pass through
  • Modify behavior — add functionality before/after the call
  • Chaining — multiple decorators can be applied

Quick Check: What is a decorator? (Answer: A function that wraps another function using a nested function)

Best Practices for Nested Functions

6

Guidelines for Using Nested Functions

# Best practices for using nested functions

# 1. Use nested functions for encapsulation
# ✅ Good - helper functions are hidden
def process_order(order):
    def validate_order(order):
        return all(key in order for key in ["items", "customer"])
    
    def calculate_total(items):
        return sum(item["price"] * item["quantity"] for item in items)
    
    if not validate_order(order):
        return "Invalid order"
    
    total = calculate_total(order["items"])
    return f"Total: ${total:.2f}"

# ❌ Bad - exposing helper functions
def validate_order(order):
    # This function is exposed to the whole module
    pass

def calculate_total(items):
    # This function is exposed to the whole module
    pass

def process_order(order):
    # Using exposed helper functions
    pass

# 2. Keep nested functions simple
# ✅ Good - short and focused
def outer():
    def inner():
        # Simple logic
        pass

# ❌ Bad - too complex
def outer():
    def inner():
        # 50 lines of complex logic
        # This should be a separate function
        pass

# 3. Use nonlocal when needed
def counter():
    count = 0
    
    def increment():
        nonlocal count  # Clearly indicate we're modifying outer variable
        count += 1
        return count
    
    return increment

# 4. Avoid deeply nested functions
# ✅ Good - two levels
def outer():
    def inner():
        pass

# ❌ Bad - three or more levels
def outer():
    def middle():
        def inner():
            pass

# 5. Use closures for state management
def create_tracker():
    """Create a closure that tracks state"""
    count = 0
    
    def track():
        nonlocal count
        count += 1
        return count
    
    return track

# 6. Document nested functions
def process_data(data):
    """
    Process data with helper functions.
    
    Args:
        data: Input data to process
    """
    
    def helper():
        """Helper function for data processing."""
        pass

Best practices summary:

  • Encapsulation — hide helper functions inside the outer function
  • Keep them simple — nested functions should be short and focused
  • Use nonlocal — clearly indicate when modifying outer variables
  • Avoid deep nesting — keep nesting to 2 levels maximum
  • Document — add docstrings to nested functions

Quick Check: What is the main reason to use a nested function? (Answer: Encapsulation and hiding helper functions)

Try It Yourself

Experiment with nested functions and closures in the editor below.

Loading Pyodide... 0%
Python Code Editor
========================================
NESTING OF FUNCTIONS PRACTICE
========================================

1. BASIC NESTED FUNCTION
Outer function called
Inner function called

2. CLOSURE WITH STATE
Counter1: 6
Counter1: 7
Counter2: 11

3. FACTORY FUNCTION
Double 5: 10
Triple 5: 15

4. DECORATOR
Before function call
After function call
Hello!

5. NESTED HELPER FUNCTIONS
Process [1, 2, 3]: [2, 4, 6]
Process [1, 'a', 3]: Invalid data

Nesting of functions practice complete!
🏆

You've Got It!

You now understand nested functions and closures in Python. You know how to use inner functions, create closures, and apply them in decorators and other advanced patterns.

Quick Quiz

Test what you've learned:

1. What is a nested function?
2. What is a closure?
3. What keyword allows modifying an outer variable?
4. What is a common use of nested functions?
5. Which of these is NOT a benefit of nested functions?

Frequently Asked Questions

What is the difference between a nested function and a closure? ▼

A nested function is simply a function defined inside another function. A closure is a nested function that captures and remembers variables from its outer scope, even after the outer function has finished executing.

Can I call a nested function from outside? ▼

No, nested functions are local to their enclosing function and cannot be called from outside. However, you can return a reference to the nested function, making it accessible through the return value.

What is the nonlocal keyword used for? ▼

nonlocal is used inside a nested function to indicate that a variable should be from the nearest enclosing scope (not global). It allows you to modify variables from the outer function.

What's a common interview question about nested functions? ▼

Common questions include: "What is a closure and how does it work?" "Explain the difference between a nested function and a closure," and "What is the nonlocal keyword used for?"

Can nested functions be recursive? ▼

Yes, nested functions can be recursive. They can call themselves just like any other function, and they have access to variables from their enclosing scope.

When should I use a nested function vs a module-level function? ▼

Use a nested function when the function is only used inside the outer function and you want encapsulation. Use a module-level function when the function needs to be reused across multiple functions or modules.

Where to Go From Here

Now that you understand nested functions and closures, check out these related topics:

Recursion

Learn about functions that call themselves.

Learn More →

Global, Local, and Non-Local

Understand variable scope in more detail.

Learn More →

Lambda Functions

Learn about anonymous functions and their use cases.

Learn More →
Interview Resources
  • Python Syntax & Variables Interview Questions
  • Top SQL Interview Questions & Answers
  • SQL Joins: Displaying Data from Multiple Tables FAQ
Previous: Function Arguments Next: Recursion →