> ## Documentation Index
> Fetch the complete documentation index at: https://python4ai.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced Functions

> Master lambdas, closures, custom decorators, and flexible arguments

Python supports multi-paradigm programming, allowing functions to be treated with first-class citizens status. Let's explore how to write advanced and powerful functions.

***

In Python, functions are first-class objects. This means that just like integers, strings, or lists, a function is an object in memory with a type, identity, and value. You can:

1. Assign them to new variables.
2. Store them inside lists, tuples, or dictionaries.
3. Pass them as arguments to other functions.
4. Access their built-in attributes (like `__name__`).

Here are examples demonstrating functions behaving as objects:

```python theme={null}
def greet(name):
    return f"Hello, {name}!"

# 1. Assign to another variable
say_hello = greet
print(say_hello("Aarav"))  # Hello, Aarav!

# 2. Store in a list and loop through them
def shout(name):
    return f"HELLO, {name}!"

actions = [greet, shout]
for action in actions:
    print(action("Amit"))  # Prints "Hello, Amit!" then "HELLO, Amit!"

# 3. Pass as an argument (Higher-Order Function)
def run_action(name, action_func):
    return action_func(name)

print(run_action("Dev", greet))  # Hello, Dev!

# 4. Access attributes and memory identity
print(greet.__name__)  # "greet"
print(id(greet))       # Memory address integer
```

***

## Lambda Functions

A **lambda** function is a small, anonymous (unnamed) function defined using the `lambda` keyword.

### Syntax

```python theme={null}
lambda arguments: expression
```

* Lambda functions can have any number of arguments, but only **one single expression**.
* The expression is evaluated and returned automatically (no `return` keyword is used).

```python theme={null}
# Standard function
def add(x, y):
    return x + y

# Lambda equivalent
add_lambda = lambda x, y: x + y

print(add_lambda(3, 7))  # 10
```

### Real-World Inline Lambda Example

For quick, single-line mapping operations like adding 18% GST to prices:

```python theme={null}
prices = [100, 250, 500]
prices_with_gst = list(map(lambda price: price * 1.18, prices))
print(prices_with_gst)  # [118.0, 295.0, 590.0]
```

### Limitations with Multi-line Logic

Python lambda functions are syntactically restricted to a **single expression**. They cannot contain statements, loops, or variable assignments.

However, you can write a **multi-line expression** (such as nested conditional ternaries) by wrapping the lambda body inside parentheses `()`.

#### 1. Multi-line Lambda Expression (Nested Conditionals)

For classifying numerical scores into categories using nested ternary expressions formatted across multiple lines:

```python theme={null}
classify_score = lambda score: (
    "Excellent" if score >= 90 else
    "Good" if score >= 75 else
    "Pass" if score >= 50 else
    "Fail"
)

print(classify_score(82))  # "Good"
print(classify_score(45))  # "Fail"
```

<Warning>
  **Bad Practice Warning:** While you *can* format single expressions across multiple lines using parentheses, writing complex nested conditionals or multi-line expressions inside a lambda is considered **bad practice**. It significantly hurts code readability and makes debugging difficult. Always prefer standard `def` functions for any logic that spans multiple lines or requires complex conditions.
</Warning>

#### 2. Normal Function Alternative for Multiple Statements

If your logic requires executing multiple separate statements (like logs, assignments, or loops), you must define a normal function using the `def` keyword:

```python theme={null}
def process_user_data(user):
    # Multiple statements cannot be done in a lambda!
    name = user.get("name", "Guest")
    cleaned_name = name.strip().title()
    print(f"Processing database record for: {cleaned_name}")
    return cleaned_name

user_record = {"name": "  aarav sharma  "}
print(process_user_data(user_record))
```

<Tip>
  Lambdas are best used as quick arguments for higher-order functions like `sorted()`, `map()`, or `filter()`:

  ```python theme={null}
  points = [(1, 2), (3, 1), (5, 0)]
  # Sort points based on the Y-coordinate (index 1 of tuple)
  sorted_points = sorted(points, key=lambda p: p[1])
  print(sorted_points)  # [(5, 0), (3, 1), (1, 2)]
  ```
</Tip>

***

## Variable-Length Arguments (`*args` and `**kwargs`)

When defining functions, you can accept an arbitrary number of arguments:

* **`*args` (Positional):** Collects additional positional arguments into a **tuple**.
* **`**kwargs` (Keyword):** Collects additional keyword arguments into a **dictionary**.

```python theme={null}
def print_everything(*args, **kwargs):
    print("Positional args:", args)
    print("Keyword args:", kwargs)

print_everything(1, 2, 3, name="Alice", age=25)
# Output:
# Positional args: (1, 2, 3)
# Keyword args: {'name': 'Alice', 'age': 25}
```

***

## Closures

A **closure** is a nested function that retains access to variables from its enclosing (outer) function's scope, even after the outer function has finished executing.

To create a closure:

1. You must have a nested function.
2. The nested function must refer to a value defined in the enclosing function.
3. The enclosing function must return the nested function.

```python theme={null}
def make_multiplier(factor):
    def multiplier(number):
        # Accesses 'factor' from outer scope
        return number * factor
    return multiplier

# Create specialized multiplier functions
double = make_multiplier(2)
triple = make_multiplier(3)

print(double(10))  # 20
print(triple(10))  # 30
```

***

## Decorators

A **decorator** is a design pattern in Python that allows you to modify or extend the behavior of a function or class without permanently changing its source code.

Under the hood, a decorator is a higher-order function that takes another function as an argument, wraps it with additional behavior, and returns the wrapper.

### Writing a Custom Decorator

```python theme={null}
# 1. Define the decorator
def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()  # Execute the original function
        print("Something is happening after the function is called.")
    return wrapper

# 2. Apply it using the @ symbol
@my_decorator
def say_hello():
    print("Hello!")

say_hello()
# Output:
# Something is happening before the function is called.
# Hello!
# Something is happening after the function is called.
```

### Decorating Functions with Arguments

To decorate functions that take arguments, use `*args` and `**kwargs` inside the wrapper function:

```python theme={null}
def log_arguments(func):
    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_arguments
def add_numbers(a, b):
    return a + b

add_numbers(10, 20)
```

***

## Practice & Exercises

To reinforce what you've learned in this section (Higher-order functions, lambdas, closures, and custom decorators), practice with these interactive notebooks:

<CardGroup cols={2}>
  <Card title="Follow-Along Practice" icon="laptop-code">
    Practice passing functions as arguments, defining single-expression anonymous lambdas, sorting coordinates dynamically, building closure scopes, and applying custom decorators.

    [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/python%20material/python4ai/public/notebooks/basics_exercises/Advanced_Functions_Practice.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/python4ai/blob/master/public/notebooks/basics_exercises/Advanced_Functions_Practice.ipynb) | <a href="/public/notebooks/basics_exercises/Advanced_Functions_Practice.ipynb" download>📥 Download</a>
  </Card>

  <Card title="Practice Exercises" icon="pen-to-square">
    Test your knowledge with hands-on exercises on higher-order operations, sorting product list prices using lambda keys, creating greeting closure builders, and writing bold-formatting decorators.

    [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/python%20material/python4ai/public/notebooks/basics_exercises/Advanced_Functions_Exercises.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/python4ai/blob/master/public/notebooks/basics_exercises/Advanced_Functions_Exercises.ipynb) | <a href="/public/notebooks/basics_exercises/Advanced_Functions_Exercises.ipynb" download>📥 Download</a>
  </Card>
</CardGroup>

## What's next?

Now let's explore Pythonic programming patterns: comprehensions, the iterator protocol, generators, and context managers!

<Card title="Comprehensions" icon="arrow-right" href="/advanced-python/comprehensions">
  Learn comprehensions, iterators, and context managers
</Card>
