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

# 03-Python Functions

> Understand function arguments, print parameters, packing/unpacking, mutable defaults, and variable scope LEGB resolution.

# Python Functions

Understanding how Python passes arguments, manages variables/scopes, and executes functions behind the scenes will help you write more predictable, efficient, and bug-free code.

## Topics Covered

In this module, you'll learn:

1. [**Function Arguments**: Positional vs Keyword argument passing](#function-arguments-positional-and-keyword-passing)
2. [**Print formatting**: `sep` and `end` parameters](#print-formatting-parameters-sep-and-end)
3. [**Packing and Unpacking**: `*` and `**` operators, `*args`, and `**kwargs`](#packing-and-unpacking)
4. [**Mutable Default Arguments Pitfall**](#mutable-default-argument-pitfall)
5. [**Variable Scope and resolution** (LEGB rule, `global` and `nonlocal` keywords)](#variable-scope)

> **Try Yourself:** [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/DSA%20With%20Python/public/notebooks/workshop-notebooks/03-python-functions/03-python-functions-exercises.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/main/public/notebooks/workshop-notebooks/03-python-functions/03-python-functions-exercises-colab.ipynb) | <a href="/notebooks/workshop-notebooks/03-python-functions/03-python-functions-exercises.ipynb" download>📥 Download</a><br />
> **Verify Solutions:** [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/DSA%20With%20Python/public/notebooks/workshop-notebooks/03-python-functions/03-python-functions.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/main/public/notebooks/workshop-notebooks/03-python-functions/03-python-functions-colab.ipynb) | <a href="/notebooks/workshop-notebooks/03-python-functions/03-python-functions.ipynb" download>📥 Download</a>

***

<div align="right">[Back to Top ↑](#topics-covered)</div>

## Function Arguments: Positional and Keyword passing

When calling functions, you can pass arguments in two ways:

1. **Positional Arguments:** Matched by position (order).
2. **Keyword Arguments:** Matched by parameter name.

```python theme={null}
def display_profile(name, age):
    print(f"Name: {name}, Age: {age}")

# Positional passing
display_profile("Alice", 25)

# Keyword passing (order does not matter)
display_profile(age=25, name="Alice")
```

Output ?

<Accordion title="Show Output">
  ```text theme={null}
  Name: Alice, Age: 25
  Name: Alice, Age: 25
  ```
</Accordion>

All positional arguments must appear before keyword arguments.

```python theme={null}
# Valid
display_profile("Alice", age=25)

# Invalid - raises SyntaxError
# display_profile(name="Alice", 25)
```

Output ?

<Accordion title="Show Output">
  ```text theme={null}
  Name: Alice, Age: 25
  ```
</Accordion>

### Exercise 1

Create a function `divide(a, b)` and call it using keyword arguments such that `b` is passed first.

<Accordion title="Solution">
  ```python theme={null}
  def divide(a, b):
      return a / b

  print(divide(b=5, a=10))
  ```
</Accordion>

***

<div align="right">[Back to Top ↑](#topics-covered)</div>

## Print Formatting Parameters: `sep` and `end`

The `print()` function contains two special parameters that format how text is displayed:

* **`sep`**: Specifies the separator character placed between multiple arguments (defaults to a space `" "`).
* **`end`**: Specifies what character is printed at the very end of the output (defaults to a newline `"\n"`).

```python theme={null}
print("Python", "FastAPI", "Uvicorn", sep=" | ")
print("Hello", end=" ")
print("World")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  Python | FastAPI | Uvicorn
  Hello World
  ```
</Accordion>

### Exercise 1

Print three words separated by a hyphen `-` using `sep`.

<Accordion title="Solution">
  ```python theme={null}
  print("one", "two", "three", sep="-")
  ```
</Accordion>

### Exercise 2

Print numbers from `1` to `5` on the same line separated by spaces using a `for` loop and the `end` argument.

<Accordion title="Solution">
  ```python theme={null}
  for i in range(1, 6):
      print(i, end=" ")
  ```
</Accordion>

***

<div align="right">[Back to Top ↑](#topics-covered)</div>

## Packing and Unpacking

Python provides the `*` and `**` operators to pack or unpack values during function calls and parameter definitions.

### Packing with `*args` and `**kwargs`

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

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

func(1, 2, name="Alice", age=25)
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  args: (1, 2)
  kwargs: {'name': 'Alice', 'age': 25}
  ```
</Accordion>

### Unpacking arguments

You can expand lists/tuples/sets using `*` and dictionaries using `**` while calling functions.

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

nums = [1, 2, 3]
print(add(*nums))

details = {"x": 10, "y": 20, "z": 30}
print(add(**details))
```

Output ?

<Accordion title="Show Output">
  ```text theme={null}
  6
  60
  ```
</Accordion>

### Exercise 1

Write a function `sum_all(*args)` that sums all positional arguments passed to it.

<Accordion title="Solution">
  ```python theme={null}
  def sum_all(*args):
      return sum(args)

  print(sum_all(1, 2, 3, 4))
  ```
</Accordion>

### Exercise 2

Explain the difference between `func(*list_val)` and `func(list_val)`.

<Accordion title="Solution">
  * `func(*list_val)` unpacks each element of `list_val` as a separate positional argument.
  * `func(list_val)` passes the entire list as a single positional argument.
</Accordion>

***

<div align="right">[Back to Top ↑](#topics-covered)</div>

## Mutable Default Argument Pitfall

When defining a function, default arguments are evaluated **only once** when the function is defined, not when it is called.

If you use a mutable default argument (like a list or a dictionary) and modify it inside the function, those modifications persist across subsequent calls.

```python theme={null}
def add_item(item, basket=[]):
    basket.append(item)
    return basket

print(add_item("Apple"))
print(add_item("Banana"))
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  ['Apple']
  ['Apple', 'Banana']
  ```
</Accordion>

### The Clean Solution

Use `None` as the default value and instantiate a new mutable object inside the function if needed.

```python theme={null}
def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

print(add_item("Apple"))
print(add_item("Banana"))
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  ['Apple']
  ['Banana']
  ```
</Accordion>

### Exercise 1

Predict the output of the following code:

```python theme={null}
def add_to_dict(key, value, my_dict={}):
    my_dict[key] = value
    return my_dict

print(add_to_dict("a", 1))
print(add_to_dict("b", 2))
```

Output ?

<Accordion title="Show Output">
  ```text theme={null}
  {'a': 1}
  {'a': 1, 'b': 2}
  ```
</Accordion>

<Accordion title="Solution">
  ```text theme={null}
  {'a': 1}
  {'a': 1, 'b': 2}
  ```

  Because the default dictionary `my_dict={}` is mutable, modifications persist across subsequent function calls.
</Accordion>

***

<div align="right">[Back to Top ↑](#topics-covered)</div>

## Variable Scope

A variable's scope determines where it can be accessed in a program. Python resolves variables using the **LEGB Rule**:

1. **L**ocal – Inside the current function.
2. **E**nclosing – Inside nested/enclosing functions.
3. **G**lobal – At the top level of the module.
4. **B**uilt-in – Built-in functions and names (e.g., `print`, `len`).

***

### Local Scope

Variables defined inside a function belong to the local scope of that function and cannot be accessed outside.

```python theme={null}
def calculate():
    result = 100
    print("Inside:", result)

calculate()
# print(result)  # NameError: name 'result' is not defined
```

Output ?

<Accordion title="Show Output">
  ```text theme={null}
  Inside: 100
  ```
</Accordion>

***

### Global Scope

Variables defined at the top level of a file are global and can be read from anywhere inside that file.

```python theme={null}
message = "Global Message"

def show():
    print(message)

show()
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  Global Message
  ```
</Accordion>

***

### The `global` Keyword

To modify a global variable inside a function, you must declare it using the `global` keyword.

```python theme={null}
count = 0

def increment():
    global count
    count += 1

increment()
print(count)
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  1
  ```
</Accordion>

***

### Enclosing Scope and the `nonlocal` Keyword

When you nest functions, the outer function's scope is the enclosing scope for the inner function. To modify a variable in the enclosing scope, use `nonlocal`.

```python theme={null}
def outer():
    count = 10

    def inner():
        nonlocal count
        count += 5
        print("Inner count:", count)

    inner()
    print("Outer count:", count)

outer()
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  Inner count: 15
  Outer count: 15
  ```
</Accordion>

### Exercise 1

Predict the output of the following code:

```python theme={null}
x = 50

def func():
    global x
    x = 2
    
func()
print(x)
```

Output ?

<Accordion title="Solution">
  ```text theme={null}
  2
  ```

  The `global x` statement inside `func()` makes any assignment to `x` modify the global variable `x`.
</Accordion>

### Exercise 2

Write a nested function where the inner function increments a variable defined in the outer function's scope by `1` and prints it.

<Accordion title="Solution">
  ```python theme={null}
  def counter():
      val = 0
      def step():
          nonlocal val
          val += 1
          print(val)
      return step

  my_step = counter()
  my_step()
  ```
</Accordion>

***

<div align="right">[Back to Top ↑](#topics-covered)</div>

## Practice

To reinforce what you've learned in this section, practice with the interactive follow-along notebook:

<CardGroup cols={1}>
  <Card title="Follow-Along Practice" icon="laptop-code">
    Practice local and global scope resolution, LEGB rules, sep and end parameters, arguments packing/unpacking, and mutable default arguments.

    [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/DSA%20With%20Python/public/notebooks/workshop-notebooks/03-python-functions/03-python-functions-exercises.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/main/public/notebooks/workshop-notebooks/03-python-functions/03-python-functions-exercises-colab.ipynb) | <a href="/notebooks/workshop-notebooks/03-python-functions/03-python-functions-exercises.ipynb" download>📥 Download</a>
  </Card>
</CardGroup>

***

<div align="right">[Back to Top ↑](#topics-covered)</div>

## Summary

In this module, you learned how Python manages variables, scopes, function parameters, and execution behind the scenes.

### Key Concepts Covered

* Positional vs Keyword function argument passing
* print parameters `sep` and `end`
* Packing and Unpacking with `*` and `**` operators
* The mutable default argument pitfall and its solution
* LEGB Variable scopes and scope resolution
* The `global` and `nonlocal` keywords
