> ## 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.

# 01-Advanced Fundamentals

> Learn advanced Python features that help you write cleaner, more expressive, and Pythonic code.

# Advanced Python Fundamentals

These features are part of modern Python and are widely used in professional applications and popular frameworks such as **FastAPI**, **Flask**, **Django**, **NumPy**, **Pandas**, and many other third-party libraries.

## Topics Covered

In this module, you'll learn:

1. [Everything is an Object & Variables as References](#everything-is-an-object-variables-as-references)
2. [Integer Value Caching (Integer Interning)](#integer-value-caching-integer-interning)
3. [Mutability (Mutable vs Immutable Objects)](#mutability-mutable-vs-immutable-objects)
4. [Floating Point Precision Nuances (`0.1 + 0.2 != 0.3`)](#floating-point-precision-nuances)
5. [Advanced String Indexing and Slicing](#string-indexing-and-slicing)
6. [Important String Operations (split, join, strip, replace)](#important-string-operations-for-data-processing)
7. [Python Nuances](#python-nuances)
   * [Short-circuit Evaluation (`and` and `or`)](#short-circuit-evaluation)
   * [Conditional Expressions (One-line `if-else`)](#conditional-expressions-one-line-if-else-ternary-operator)
   * [Chained Comparisons](#chained-comparisons)
   * [Multiple Assignment and Variable Swapping](#multiple-assignment-and-variable-swapping)
8. [`for-else` and `while-else`](#for-else-and-while-else)
9. [Function Arguments](#function-arguments)
   * [Positional Arguments](#positional-arguments)
   * [Keyword Arguments](#keyword-arguments)
   * [Mixing Positional and Keyword Arguments](#mixing-positional-and-keyword-arguments)
   * [Positional-only Parameters (`/`)](#positional-only-parameters)
   * [Keyword-only Parameters (`*`)](#keyword-only-parameters)
10. [Packing and Unpacking](#packing-and-unpacking)
    * [Packing Positional Arguments (`*args`)](#packing-positional-arguments-args)
    * [Packing Keyword Arguments (`**kwargs`)](#packing-keyword-arguments-kwargs)
    * [Unpacking Positional Arguments (`*`)](#unpacking-positional-arguments)
    * [Unpacking Keyword Arguments (`**`)](#unpacking-keyword-arguments)
    * [Extended Unpacking](#extended-unpacking)
    * [Combining Different Parameter Types](#combining-everything)

By the end of this module, you'll be comfortable writing more flexible, reusable, and Pythonic functions that are commonly used in real-world Python applications.

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

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

## Everything is an Object & Variables as References

In Python, **everything is treated as an object**—including integers, floats, strings, functions, and modules.

Furthermore, **variables in Python do not hold values directly**. Instead, variables hold **references** (pointers) to the location in memory where the object is stored.

### Understanding Object Identity

You can use the built-in `id()` function to find the memory address of an object. The `is` operator checks if two references point to the exact same object in memory, while `==` checks if their actual values are equal.

```python theme={null}
x = [1, 2, 3]
y = x

print(f"ID of x: {id(x)}")
print(f"ID of y: {id(y)}")
print(x is y)
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  ID of x: 4356781296
  ID of y: 4356781296
  True
  ```
</Accordion>

If you create an identical list independently, they will have different identities despite having equal values:

```python theme={null}
x = [1, 2, 3]
z = [1, 2, 3]

print(x == z)
print(x is z)
```

Output

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

### Verification: Integers and Floats are Objects

Unlike languages like C++ or Java where primitive types (such as `int`, `double`) store raw values directly in variables, in Python, **even integers and floats are objects**.

This has two major implications:

1. Variables assigned to integers or floats store **references** (pointers) to those numeric objects in memory.
2. Integers and floats have their own built-in **properties and methods** that you can invoke using dot notation.

#### 1. Verifying Reference Behavior with Float Objects

When you create two floats independently, Python creates two distinct objects in memory. The variables `f1` and `f2` store references to these distinct objects:

```python theme={null}
f1 = 2.5
f2 = 2.5

print("Value Equality (f1 == f2):", f1 == f2)
print("Object Identity (f1 is f2):", f1 is f2)
print("ID of f1:", id(f1))
print("ID of f2:", id(f2))
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  Value Equality (f1 == f2): True
  Object Identity (f1 is f2): False
  ID of f1: 4395648112
  ID of f2: 4395648240
  ```
</Accordion>

***

#### 2. Accessing Methods on Numbers

Since numbers are objects, they have built-in methods.

> \[!NOTE]
> If you call a method directly on a literal number, you must enclose the number in parentheses (e.g., `(10).bit_length()`). Otherwise, Python's parser will confuse the dot `.` with a decimal point.

##### Built-in Methods on Integers:

* **`bit_length()`**: Returns the number of bits required to represent an integer in binary.
* **`as_integer_ratio()`**: Returns a tuple of `(numerator, denominator)` representing the integer as a fraction.

```python theme={null}
num = 42
print("Bits required for 42:", num.bit_length())  # 42 is binary 101010
print("Fraction representation:", num.as_integer_ratio())
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  Bits required for 42: 6
  Fraction representation: (42, 1)
  ```
</Accordion>

##### Built-in Methods on Floats:

* **`is_integer()`**: Returns `True` if the float has no fractional part (e.g., `10.0`).
* **`as_integer_ratio()`**: Returns the exact fraction representing the float.

```python theme={null}
f_val = 10.5
print("Is 10.5 an integer?", f_val.is_integer())
print("Is 3.0 an integer?", (3.0).is_integer())
print("Fraction representation:", f_val.as_integer_ratio())
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  Is 10.5 an integer? False
  Is 3.0 an integer? True
  Fraction representation: (21, 2)
  ```
</Accordion>

***

### Exercise 1

Assign the string `"FastAPI"` to a variable `a`, and then assign `a` to `b`. Verify if they point to the same object using `is` and by printing their `id`s.

<Accordion title="Solution">
  ```python theme={null}
  a = "FastAPI"
  b = a

  print(a is b)
  print(id(a) == id(b))
  ```
</Accordion>

### Exercise 2

Create two float variables with the value `2.5` independently. Compare them using `==` and `is`, and explain the result.

<Accordion title="Solution">
  ```python theme={null}
  f1 = 2.5
  f2 = 2.5

  print(f1 == f2)  # True (values are equal)
  print(f1 is f2)  # False (different float objects in memory)
  ```
</Accordion>

***

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

## Integer Value Caching (Integer Interning)

In Python, memory optimization is built into the interpreter. One of the most famous optimizations is **Integer Value Caching** (also known as integer interning).

At startup, Python (specifically the standard CPython implementation) pre-allocates and caches all integers in the range **`[-5, 256]`**.

When you reference any integer in this range, Python does not create a new object. Instead, it returns a reference to the existing cached integer object.

```python theme={null}
a = 100
b = 100

print(a is b)
print(id(a) == id(b))
```

Output

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

However, for integers outside this range, Python creates a new object in memory (unless optimized within the same code block by the compiler).

```python theme={null}
x = 300
y = 300

print(x is y)
```

Output

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

### Exercise 1

Predict the output of the following comparisons:

```python theme={null}
a = 256
b = 256
print(a is b)

x = -6
y = -6
print(x is y)
```

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

  `256` lies within the cache range `[-5, 256]`, so `a` and `b` refer to the same object. `-6` is outside the cache range, so `x` and `y` refer to different objects.
</Accordion>

### Exercise 2

Write a code snippet to verify that floats are **not** cached in the same manner as small integers (e.g., compare `1.0` and `1.0` using `is`).

<Accordion title="Solution">
  ```python theme={null}
  f1 = 1.0
  f2 = 1.0

  # This will print False because float caching is not pre-allocated like small integers
  print(f1 is f2)
  ```
</Accordion>

***

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

## Mutability (Mutable vs Immutable Objects)

In Python, every object is classified as either **mutable** or **immutable**. Understanding this distinction is crucial for understanding how Python handles variable assignment, function arguments, and memory.

### Immutable Objects

An immutable object's state **cannot be changed** after it is created. Examples of immutable types in Python:

* Numeric types (`int`, `float`, `complex`)
* Strings (`str`)
* Tuples (`tuple`)
* Booleans (`bool`)
* Frozensets (`frozenset`)

If you attempt to modify an immutable object, Python does not change the original object; instead, it creates a new object in memory and updates the reference.

```python theme={null}
x = 10
print(f"Initial ID of x: {id(x)}")

x = x + 5
print(f"New ID of x: {id(x)}")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  Initial ID of x: 4376378704
  New ID of x: 4376378864
  ```

  *(Note: The exact IDs will vary across runs, but they will be different).*
</Accordion>

### Mutable Objects

A mutable object's state **can be changed** in-place after it is created. Examples of mutable types in Python:

* Lists (`list`)
* Dictionaries (`dict`)
* Sets (`set`)

Modifying a mutable object retains the same memory address (`id`).

```python theme={null}
lst = [1, 2, 3]
print(f"Initial ID of lst: {id(lst)}")

lst.append(4)
print(f"New ID of lst: {id(lst)}")
print(f"List content: {lst}")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  Initial ID of lst: 4377983168
  New ID of lst: 4377983168
  List content: [1, 2, 3, 4]
  ```

  *(Note: The ID remains identical, showing the object was modified in-place).*
</Accordion>

### A Common Nuance: Mutable Objects Inside Immutable Containers

If a tuple contains a mutable object, such as a list, the tuple itself is still immutable (its references cannot change), but the list *inside* it can be mutated.

```python theme={null}
t = (1, 2, [3, 4])
t[2].append(5)
print(t)
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  (1, 2, [3, 4, 5])
  ```
</Accordion>

### Exercise 1

Predict whether the following operation is valid and what it outputs:

```python theme={null}
my_str = "hello"
my_str[0] = "H"
```

<Accordion title="Solution">
  ```text theme={null}
  TypeError: 'str' object does not support item assignment
  ```

  Strings are immutable, so you cannot mutate individual characters in-place.
</Accordion>

### Exercise 2

What is the final content of `list_b`?

```python theme={null}
list_a = [1, 2, 3]
list_b = list_a
list_a.append(4)
```

<Accordion title="Solution">
  ```python theme={null}
  [1, 2, 3, 4]
  ```

  Since lists are mutable and assignment (`list_b = list_a`) copies the reference, both variables point to the same list object in memory.
</Accordion>

***

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

## Floating Point Precision Nuances

Floating-point numbers in computers are represented as binary fractions. This leads to some surprising behavior when performing decimal calculations.

### The Classic Floating-Point Issue: `0.1 + 0.2`

In Python:

```python theme={null}
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
```

Output

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

### Why Does This Happen?

Numbers like `0.1` and `0.2` have infinite repeating representations in binary (similar to how $1/3$ is $0.33333...$ in base 10). The computer must truncate these values, introducing a microscopic rounding error. When you add them, the errors combine, resulting in `0.30000000000000004`.

### Why Does `0.5 + 0.25 == 0.75` Work?

Unlike `0.1` and `0.2`, the fractions `0.5` ($2^{-1}$), `0.25` ($2^{-2}$), and `0.75` ($2^{-1} + 2^{-2}$) are sums of exact powers of 2. They can be represented perfectly in binary.

```python theme={null}
print(0.5 + 0.25)
print(0.5 + 0.25 == 0.75)
```

Output

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

### How to Handle Exact Decimal Math

If your application requires exact decimal calculations (e.g., handling money or financial transactions), use Python's built-in `decimal` module:

```python theme={null}
from decimal import Decimal

val1 = Decimal('0.1')
val2 = Decimal('0.2')
print(val1 + val2)
print(val1 + val2 == Decimal('0.3'))
```

Output

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

*(Note: Always pass string representations to `Decimal` constructor. Passing floats like `Decimal(0.1)` preserves the float's floating-point precision error).*

### Exercise 1

Predict the output of:

```python theme={null}
from decimal import Decimal
print(Decimal(0.1) == Decimal('0.1'))
```

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

  `Decimal(0.1)` inherits the imprecise representation of the float `0.1`, whereas `Decimal('0.1')` represents exactly `0.1`.
</Accordion>

### Exercise 2

Will the expression `0.125 + 0.125 == 0.25` be `True` or `False`?

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

  `0.125` ($2^{-3}$) and `0.25` ($2^{-2}$) can be represented exactly in binary, so no rounding error occurs.
</Accordion>

***

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

## String Indexing and Slicing

Strings are sequences of characters. Python allows extracting portions of a string using **indexing** and **slicing**.

```python theme={null}
string[start:stop:step]
```

### Understanding the Slice Components

* **start** – Starting index (inclusive)
* **stop** – Ending index (exclusive)
* **step** – Number of characters to skip

Any of these values may be omitted.

### Basic Slicing

```python theme={null}
text = "Advanced Python"

print(text[0])
print(text[3])
print(text[-1])
print(text[-2])
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  A
  a
  n
  o
  ```
</Accordion>

### Extracting a Portion of a String

```python theme={null}
text = "Advanced Python"

print(text[0:8])
print(text[9:15])
```

Output

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

### Omitting Start or Stop

```python theme={null}
text = "Advanced Python"

print(text[:8])
print(text[9:])
```

Output

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

### Using Step

```python theme={null}
text = "Programming"

print(text[::2])
print(text[1::2])
```

Output

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

### Reversing a String

A negative step traverses the string from right to left.

```python theme={null}
text = "Python"

print(text[::-1])
```

Output

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

### Practical Example

Extract the file extension.

```python theme={null}
filename = "report.pdf"

print(filename[-3:])
```

Output

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

### Exercise 1

Extract `"Python"` from the following string.

```python theme={null}
text = "Learn Python Programming"
```

<Accordion title="Solution">
  ```python theme={null}
  text = "Learn Python Programming"

  print(text[6:12])
  ```
</Accordion>

### Exercise 2

Reverse the following string using slicing.

```python theme={null}
text = "Artificial Intelligence"
```

<Accordion title="Solution">
  ```python theme={null}
  text = "Artificial Intelligence"

  print(text[::-1])
  ```
</Accordion>

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

## Important String Operations for Data Processing

In real-world data processing, indexing and slicing are rarely enough. We frequently need to clean, transform, partition, and format string data.

### 1. Cleaning Whitespace with `.strip()`

Whitespace at the beginning or end of strings (like spaces, tabs, or newlines) is common in raw data imports.

* `.strip()`: Removes leading and trailing whitespace.
* `.lstrip()`: Removes leading whitespace only.
* `.rstrip()`: Removes trailing whitespace only.

```python theme={null}
raw_data = "   \n  user_name_123   \t "
clean_data = raw_data.strip()
print(f"Original: {repr(raw_data)}")
print(f"Cleaned: {repr(clean_data)}")
```

### 2. Splitting and Joining

Converting between single strings and lists of substrings is a core data-processing pattern.

#### Splitting Strings (`.split()`)

The `.split(sep)` method splits a string on a specified separator and returns a list of substrings. If no separator is specified, it splits on any consecutive whitespace.

```python theme={null}
csv_row = "Alice,25,Engineer,Hyderabad"
parsed_list = csv_row.split(",")
print("Parsed List:", parsed_list)

sentence = "Python  is   fun"
words = sentence.split()  # Splits on multiple spaces
print("Words:", words)
```

#### Joining Lists (`.join()`)

The `.join(iterable)` method is called on the separator string and merges a list of strings into a single string.

```python theme={null}
words = ["Python", "FastAPI", "Uvicorn"]
joined_pipe = " | ".join(words)
print("Joined:", joined_pipe)

csv_line = ",".join(["Bob", "30", "Manager"])
print("CSV:", csv_line)
```

### 3. Replacing Substrings (`.replace()`)

You can swap out characters or substrings using `.replace(old, new)`.

```python theme={null}
file_path = "data/2024/report.csv"
url_path = file_path.replace("/", "-")
print("New string:", url_path)
```

### 4. Validating Prefix/Suffix (`.startswith()` and `.endswith()`)

Useful for filtering filenames, URLs, or protocols.

```python theme={null}
filename = "data_export.xlsx"
print(filename.startswith("data_"))
print(filename.endswith(".xlsx"))
```

### Exercise 3

Clean a list of raw email strings by removing leading/trailing whitespaces and converting them to lowercase.

<Accordion title="Solution">
  ```python theme={null}
  raw_emails = ["  Alice@Test.Com ", "bob@Domain.org\n", "\tcharlie@test.com  "]
  cleaned_emails = [email.strip().lower() for email in raw_emails]
  print(cleaned_emails)
  ```
</Accordion>

### Exercise 4

Given a raw CSV row `"john doe, 28, Developer, New York"`, parse the columns, strip the whitespace, capitalize the name (`"John Doe"`), and join the columns back using a semicolon `;` as the separator.

<Accordion title="Solution">
  ```python theme={null}
  raw_row = "john doe, 28, Developer, New York"
  # Split by comma
  columns = raw_row.split(",")
  # Clean columns
  cleaned = [col.strip() for col in columns]
  # Capitalize the name (first column)
  cleaned[0] = cleaned[0].title()
  # Join using semicolon
  final_row = ";".join(cleaned)
  print(final_row)
  ```
</Accordion>

***

# Python Nuances

Python provides several elegant language features that simplify common programming tasks.

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

## Short-Circuit Evaluation

Logical operators don't always evaluate every expression.

### Using `or`

Suppose a user doesn't enter a name.

```python theme={null}
username = ""

if username:
    print(username)
else:
    print("Guest")
```

The same logic can be written more concisely.

```python theme={null}
username = ""

print(username or "Guest")
```

Output

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

If a username exists,

```python theme={null}
username = "Alice"

print(username or "Guest")
```

Output

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

### Using `and`

Suppose a user must be logged in before displaying a welcome message.

```python theme={null}
logged_in = True

if logged_in:
    print("Welcome!")
```

Python allows this shorter form.

```python theme={null}
logged_in = True

logged_in and print("Welcome!")
```

If `logged_in` is `False`, the second expression is never evaluated.

### Exercise 1

Print `"Anonymous"` whenever `name` is an empty string.

<Accordion title="Solution">
  ```python theme={null}
  name = ""

  print(name or "Anonymous")
  ```
</Accordion>

### Exercise 2

Print `"Access Granted"` only when `is_admin` is `True`.

<Accordion title="Solution">
  ```python theme={null}
  is_admin = True

  is_admin and print("Access Granted")
  ```
</Accordion>

### One-line `if` only statement

If you want to run a single statement on a condition without an `else`, you can write it on one line (though this is a statement, not an expression that returns a value):

```python theme={null}
is_ready = True
if is_ready: print("Ready!")
```

### Exercise 1

Write a one-line conditional expression that assigns `"Even"` or `"Odd"` to a variable `label` based on whether `num` is divisible by 2.

<Accordion title="Solution">
  ```python theme={null}
  num = 7
  label = "Even" if num % 2 == 0 else "Odd"
  print(label)
  ```
</Accordion>

### Exercise 2

Write a one-line conditional expression to assign `"Pass"` to a variable `result` if `score >= 50` else `"Fail"`.

<Accordion title="Solution">
  ```python theme={null}
  score = 85
  result = "Pass" if score >= 50 else "Fail"
  print(result)
  ```
</Accordion>

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

## Conditional Expressions (One-line `if-else` / Ternary Operator)

A **conditional expression** (also known as a ternary operator) allows you to assign a value to a variable based on a condition in a single line.

### Syntax

```python theme={null}
value_if_true if condition else value_if_false
```

Traditional `if-else` blocks:

```python theme={null}
age = 20
if age >= 18:
    status = "Adult"
else:
    status = "Minor"
print(status)
```

Can be written cleanly as:

```python theme={null}
age = 20
status = "Adult" if age >= 18 else "Minor"
print(status)
```

Output

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

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

## Chained Comparisons

Many range checks require two comparisons.

Traditional approach:

```python theme={null}
age = 25

if age >= 18 and age <= 60:
    print("Eligible")
```

Python provides a cleaner syntax.

```python theme={null}
age = 25

if 18 <= age <= 60:
    print("Eligible")
```

This syntax is easier to read and is preferred in Python.

Another example:

```python theme={null}
marks = 82

print(40 <= marks <= 100)
```

Checking whether a character is lowercase.

```python theme={null}
ch = "m"

print("a" <= ch <= "z")
```

### Exercise 1

Check whether `temperature` lies between `20` and `35`.

<Accordion title="Solution">
  ```python theme={null}
  temperature = 28

  print(20 <= temperature <= 35)
  ```
</Accordion>

### Exercise 2

Check whether a character is an uppercase alphabet.

<Accordion title="Solution">
  ```python theme={null}
  ch = "P"

  print("A" <= ch <= "Z")
  ```
</Accordion>

***

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

## Multiple Assignment and Variable Swapping

### Multiple Assignment

Instead of assigning each variable separately,

```python theme={null}
language = "Python"
version = "Python"
author = "Python"
```

Python allows

```python theme={null}
language = version = author = "Python"

print(language)
print(version)
print(author)
```

### Assigning Multiple Values

```python theme={null}
first_name, last_name = "John", "Doe"

print(first_name)
print(last_name)
```

### Swapping Variables

Traditional approach:

```python theme={null}
first = "Hello"
second = "World"

temp = first
first = second
second = temp
```

Python provides a much cleaner solution.

```python theme={null}
first = "Hello"
second = "World"

first, second = second, first

print(first)
print(second)
```

Output

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

### Exercise 1

Assign `"Unknown"` to three variables using a single statement.

<Accordion title="Solution">
  ```python theme={null}
  city = state = country = "Unknown"
  ```
</Accordion>

### Exercise 2

Swap the values of two variables without using a temporary variable.

<Accordion title="Solution">
  ```python theme={null}
  language = "Python"
  framework = "FastAPI"

  language, framework = framework, language

  print(language)
  print(framework)
  ```
</Accordion>

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

## `for-else` and `while-else`

Unlike many programming languages, Python allows an optional `else` block after loops.

The `else` block executes **only when the loop completes normally without encountering a `break` statement**.

### Example 1: Searching for a Divisor

Suppose we want to check whether a number has any divisor other than `1` and itself.

```python theme={null}
number = 21

for i in range(2, number):
    if number % i == 0:
        print(f"{i} is a divisor.")
        break
else:
    print("No divisor found.")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  3 is a divisor.
  ```
</Accordion>

If a divisor is found, the loop terminates using `break`, so the `else` block is skipped.

***

### Example 2: Checking Whether a Number is Prime

A prime number has no divisors other than `1` and itself.

```python theme={null}
number = 29

for i in range(2, int(number ** 0.5) + 1):
    if number % i == 0:
        print("Not Prime")
        break
else:
    print("Prime")
```

Output

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

This is one of the most common real-world uses of `for-else`.

***

### `while-else`

The `else` block also works with `while` loops.

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

while count <= 5:
    print(count)
    count += 1
else:
    print("Loop Finished")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  1
  2
  3
  4
  5
  Loop Finished
  ```
</Accordion>

***

### Exercise 1

Write a program to check whether a given number is a **perfect square** using `for-else`.

<Accordion title="Solution">
  ```python theme={null}
  number = 49

  for i in range(1, number + 1):
      if i * i == number:
          print("Perfect Square")
          break
  else:
      print("Not a Perfect Square")
  ```
</Accordion>

***

### Exercise 2

Print all prime numbers between `50` and `100` using `for-else`.

<Accordion title="Solution">
  ```python theme={null}
  start = 50
  end = 100

  for number in range(start, end + 1):
      if number < 2:
          continue

      for i in range(2, int(number ** 0.5) + 1):
          if number % i == 0:
              break
      else:
          print(number)
  ```
</Accordion>

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

## Function Arguments

Functions become more flexible when they can accept arguments in different ways. Python supports positional arguments, keyword arguments, positional-only parameters, and keyword-only parameters.

***

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

## Positional Arguments

Positional arguments are matched with function parameters **based on their position**.

```python theme={null}
def introduce(name, city):
    print(f"My name is {name}.")
    print(f"I live in {city}.")

introduce("Alice", "Hyderabad")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  My name is Alice.
  I live in Hyderabad.
  ```
</Accordion>

Here,

* `"Alice"` is assigned to `name`
* `"Hyderabad"` is assigned to `city`

The order of the arguments is important.

```python theme={null}
introduce("Hyderabad", "Alice")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  My name is Hyderabad.
  I live in Alice.
  ```
</Accordion>

### Exercise 1

Create a function `greet()` that accepts a person's name and prints a welcome message.

<Accordion title="Solution">
  ```python theme={null}
  def greet(name):
      print(f"Welcome {name}")

  greet("John")
  ```
</Accordion>

### Exercise 2

Create a function that accepts a student's name and course, then displays both values.

<Accordion title="Solution">
  ```python theme={null}
  def student(name, course):
      print(name)
      print(course)

  student("Alice", "Python")
  ```
</Accordion>

***

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

## Keyword Arguments

Keyword arguments pass values using **parameter names** instead of their positions.

```python theme={null}
def introduce(name, city):
    print(f"My name is {name}.")
    print(f"I live in {city}.")

introduce(city="Hyderabad", name="Alice")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  My name is Alice.
  I live in Hyderabad.
  ```
</Accordion>

Since parameter names are used, the order no longer matters.

Keyword arguments improve readability, especially for functions having many parameters.

### Exercise 1

Call the following function using keyword arguments.

```python theme={null}
def employee(name, department):
    print(name, department)
```

<Accordion title="Solution">
  ```python theme={null}
  def employee(name, department):
      print(name, department)

  employee(department="HR", name="David")
  ```
</Accordion>

### Exercise 2

Create a function that accepts a product name and price. Call it using keyword arguments.

<Accordion title="Solution">
  ```python theme={null}
  def product(name, price):
      print(name)
      print(price)

  product(price=2500, name="Keyboard")
  ```
</Accordion>

***

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

## Mixing Positional and Keyword Arguments

Positional and keyword arguments can be used together.

```python theme={null}
def introduce(name, city, profession):
    print(name)
    print(city)
    print(profession)

introduce("Alice", city="Hyderabad", profession="Developer")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  Alice
  Hyderabad
  Developer
  ```
</Accordion>

### Rule

All positional arguments **must appear before** keyword arguments.

✔️ Correct

```python theme={null}
introduce("Alice", city="Hyderabad", profession="Developer")
```

❌ Incorrect

```python theme={null}
# introduce(name="Alice", "Hyderabad", profession="Developer")
```

The second call raises a `SyntaxError` because a positional argument appears after a keyword argument.

### Exercise 1

Create a function that accepts a student's name, course, and city. Pass the first argument positionally and the remaining arguments using keywords.

<Accordion title="Solution">
  ```python theme={null}
  def student(name, course, city):
      print(name)
      print(course)
      print(city)

  student("Alice", course="Python", city="Hyderabad")
  ```
</Accordion>

### Exercise 2

Create a function that accepts an employee's name, designation, and salary. Mix positional and keyword arguments while calling it.

<Accordion title="Solution">
  ```python theme={null}
  def employee(name, designation, salary):
      print(name)
      print(designation)
      print(salary)

  employee("John", designation="Manager", salary=65000)
  ```
</Accordion>

***

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

## Positional-only Parameters (`/`)

Python allows certain parameters to be passed **only by position**.

The `/` symbol separates positional-only parameters from the remaining parameters.

```python theme={null}
def divide(a, b, /):
    return a / b

print(divide(10, 2))
```

Valid

```python theme={null}
divide(20, 5)
```

Invalid

```python theme={null}
# divide(a=20, b=5)
```

The second call raises a `TypeError`.

### Why Use Positional-only Parameters?

Sometimes parameter names are implementation details and should not become part of the public interface.

For example,

```python theme={null}
def power(base, exponent, /):
    return base ** exponent

print(power(2, 5))
```

The function works correctly regardless of the internal parameter names.

### Exercise 1

Create a function `multiply()` that accepts two positional-only parameters.

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

  print(multiply(10, 5))
  ```
</Accordion>

### Exercise 2

Create a function `discount()` that accepts price and discount percentage as positional-only parameters.

<Accordion title="Solution">
  ```python theme={null}
  def discount(price, percentage, /):
      return price - (price * percentage / 100)

  print(discount(5000, 20))
  ```
</Accordion>

***

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

## Keyword-only Parameters (`*`)

Parameters appearing after `*` must always be passed using **keyword arguments**.

```python theme={null}
def register(name, *, city, email):
    print(name)
    print(city)
    print(email)

register(
    "Alice",
    city="Hyderabad",
    email="alice@test.com"
)
```

Valid

```python theme={null}
register(
    "John",
    city="Delhi",
    email="john@test.com"
)
```

Invalid

```python theme={null}
# register("John", "Delhi", "john@test.com")
```

The second call raises a `TypeError`.

### Why Use Keyword-only Parameters?

Keyword arguments improve readability, especially when a function has several optional settings.

```python theme={null}
def connect(host, *, port, timeout):
    print(host)
    print(port)
    print(timeout)

connect(
    "localhost",
    port=8000,
    timeout=30
)
```

The purpose of each value is immediately clear.

### Combining Both

A function can use positional-only and keyword-only parameters together.

```python theme={null}
def calculate(a, b, /, *, operation):
    if operation == "add":
        return a + b
    elif operation == "subtract":
        return a - b

print(calculate(10, 20, operation="add"))
```

### Exercise 1

Create a function where the first parameter is positional-only and the second parameter is keyword-only.

<Accordion title="Solution">
  ```python theme={null}
  def display(name, /, *, city):
      print(name)
      print(city)

  display("Alice", city="Hyderabad")
  ```
</Accordion>

### Exercise 2

Create a function that calculates the area of a rectangle using keyword-only parameters.

<Accordion title="Solution">
  ```python theme={null}
  def area(*, length, width):
      return length * width

  print(area(length=10, width=5))
  ```
</Accordion>

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

## Packing and Unpacking

The operators `*` and `**` have two complementary roles in Python.

* **Packing** collects multiple values into a single variable.
* **Unpacking** expands a collection into individual values.

Although the same symbols are used, their behavior depends on the context.

***

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

## Packing Positional Arguments (`*args`)

Sometimes we don't know how many positional arguments a function will receive.

`*args` collects all remaining positional arguments into a tuple.

```python theme={null}
def display(*values):
    print(values)

display("Python")
display("Python", "Java", "C++")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  ('Python',)
  ('Python', 'Java', 'C++')
  ```
</Accordion>

A practical example:

```python theme={null}
def total(*numbers):
    result = 0

    for number in numbers:
        result += number

    return result

print(total(10, 20))
print(total(10, 20, 30))
print(total(10, 20, 30, 40))
```

Output

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

### Exercise 1

Write a function that accepts any number of names and prints them.

<Accordion title="Solution">
  ```python theme={null}
  def show_names(*names):
      for name in names:
          print(name)

  show_names("Alice", "Bob", "Charlie")
  ```
</Accordion>

### Exercise 2

Write a function that accepts any number of integers and returns the largest value.

<Accordion title="Solution">
  ```python theme={null}
  def largest(*numbers):
      return max(numbers)

  print(largest(12, 45, 9, 32))
  ```
</Accordion>

***

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

## Packing Keyword Arguments (`**kwargs`)

`**kwargs` collects keyword arguments into a dictionary.

```python theme={null}
def display(**details):
    print(details)

display(name="Alice", city="Hyderabad")
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  {'name': 'Alice', 'city': 'Hyderabad'}
  ```
</Accordion>

Access individual values.

```python theme={null}
def display(**details):
    for key, value in details.items():
        print(f"{key}: {value}")

display(
    name="Alice",
    city="Hyderabad",
    course="Python"
)
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  name: Alice
  city: Hyderabad
  course: Python
  ```
</Accordion>

### Exercise 1

Write a function that accepts any number of student details and prints them.

<Accordion title="Solution">
  ```python theme={null}
  def student(**details):
      for key, value in details.items():
          print(key, value)

  student(
      name="John",
      course="Python",
      city="Delhi"
  )
  ```
</Accordion>

### Exercise 2

Write a function that prints all keyword arguments passed to it.

<Accordion title="Solution">
  ```python theme={null}
  def display(**kwargs):
      for key, value in kwargs.items():
          print(key, value)

  display(
      language="Python",
      version="3.12"
  )
  ```
</Accordion>

***

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

## Unpacking Positional Arguments (`*`)

Packing collects values.

Unpacking performs the opposite operation.

Instead of passing arguments one by one,

```python theme={null}
def greet(name, city):
    print(name)
    print(city)

greet("Alice", "Hyderabad")
```

the values can be stored in a tuple and unpacked.

```python theme={null}
def greet(name, city):
    print(name)
    print(city)

details = ("Alice", "Hyderabad")

greet(*details)
```

The `*` operator expands the tuple into individual arguments.

### Exercise 1

Call a function by unpacking a tuple.

<Accordion title="Solution">
  ```python theme={null}
  def employee(name, department):
      print(name)
      print(department)

  emp = ("David", "HR")

  employee(*emp)
  ```
</Accordion>

### Exercise 2

Store two numbers in a tuple and unpack them while calling a function.

<Accordion title="Solution">
  ```python theme={null}
  def add(a, b):
      print(a + b)

  numbers = (10, 20)

  add(*numbers)
  ```
</Accordion>

***

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

## Unpacking Keyword Arguments (`**`)

A dictionary can also be unpacked into keyword arguments.

```python theme={null}
def student(name, city):
    print(name)
    print(city)

details = {
    "name": "Alice",
    "city": "Hyderabad"
}

student(**details)
```

The dictionary keys must match the parameter names.

### Exercise 1

Create a dictionary containing product details and unpack it while calling a function.

<Accordion title="Solution">
  ```python theme={null}
  def product(name, price):
      print(name)
      print(price)

  details = {
      "name": "Keyboard",
      "price": 2500
  }

  product(**details)
  ```
</Accordion>

### Exercise 2

Call a function by unpacking a dictionary containing employee details.

<Accordion title="Solution">
  ```python theme={null}
  def employee(name, department):
      print(name)
      print(department)

  details = {
      "name": "John",
      "department": "Accounts"
  }

  employee(**details)
  ```
</Accordion>

***

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

## Extended Unpacking

Python allows collecting the remaining values using `*`.

```python theme={null}
text = "PYTHON"

first, *middle, last = text

print(first)
print(middle)
print(last)
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  P
  ['Y', 'T', 'H', 'O']
  N
  ```
</Accordion>

This is useful when only the first and last elements are important.

Another example.

```python theme={null}
text = "Programming"

first, *remaining = text

print(first)
print(remaining)
```

### Exercise 1

Extract the first and last characters of the string `"Developer"`.

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

  first, *middle, last = text

  print(first)
  print(last)
  ```
</Accordion>

### Exercise 2

Extract the first character separately and store the remaining characters in another variable.

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

  first, *remaining = text

  print(first)
  print(remaining)
  ```
</Accordion>

***

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

## Combining Everything

Python allows all parameter types to be used together.

```python theme={null}
def register(username, *courses, active=True, **details):
    print("Username :", username)
    print("Courses  :", courses)
    print("Active   :", active)
    print("Details  :", details)

register(
    "Alice",
    "Python",
    "FastAPI",
    active=False,
    city="Hyderabad",
    experience=2
)
```

Output

<Accordion title="Show Output">
  ```text theme={null}
  Username : Alice
  Courses  : ('Python', 'FastAPI')
  Active   : False
  Details  : {'city': 'Hyderabad', 'experience': 2}
  ```
</Accordion>

This pattern is commonly used in Python libraries and frameworks.

***

### Exercise 1

Write a function that accepts a student's name followed by any number of marks and prints the average.

<Accordion title="Solution">
  ```python theme={null}
  def average(name, *marks):
      total = sum(marks)
      avg = total / len(marks)

      print(name)
      print(avg)

  average("Alice", 80, 85, 92)
  ```
</Accordion>

### Exercise 2

Create a function that accepts a required username, any number of hobbies, and additional user details.

<Accordion title="Solution">
  ```python theme={null}
  def profile(username, *hobbies, **details):
      print(username)
      print(hobbies)
      print(details)

  profile(
      "John",
      "Reading",
      "Gaming",
      city="Delhi",
      age=24
  )
  ```
</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 string slicing, short-circuit logic, chained comparisons, variable swapping, loop-else clauses, and packing/unpacking positional and keyword arguments.

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

***

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

## Summary

In this module, you learned several advanced Python features that make your code cleaner, more expressive, and more reusable.

### Key Concepts Covered

* Advanced string indexing and slicing
* Short-circuit evaluation using `and` and `or`
* Chained comparisons
* Multiple assignment and variable swapping
* `for-else` and `while-else`
* Positional arguments
* Keyword arguments
* Mixing positional and keyword arguments
* Positional-only parameters (`/`)
* Keyword-only parameters (`*`)
* Packing positional arguments using `*args`
* Packing keyword arguments using `**kwargs`
* Unpacking tuples using `*`
* Unpacking dictionaries using `**`
* Extended unpacking
* Combining different parameter types in a single function
