Skip to main content

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
  2. Integer Value Caching (Integer Interning)
  3. Mutability (Mutable vs Immutable Objects)
  4. Floating Point Precision Nuances (0.1 + 0.2 != 0.3)
  5. Advanced String Indexing and Slicing
  6. Important String Operations (split, join, strip, replace)
  7. Python Nuances
  8. for-else and while-else
  9. Function Arguments
  10. Packing and Unpacking
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 | 🚀 Colab | 📥 Download
Verify Solutions: 💻 VS Code | 🚀 Colab | 📥 Download

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.
Output
If you create an identical list independently, they will have different identities despite having equal values:
Output

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:
Output

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

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

Exercise 2

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

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.
Output
However, for integers outside this range, Python creates a new object in memory (unless optimized within the same code block by the compiler).
Output

Exercise 1

Predict the output of the following comparisons:
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.

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

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.
Output
(Note: The exact IDs will vary across runs, but they will be different).

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).
Output
(Note: The ID remains identical, showing the object was modified in-place).

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

Exercise 1

Predict whether the following operation is valid and what it outputs:
Strings are immutable, so you cannot mutate individual characters in-place.

Exercise 2

What is the final content of list_b?
Since lists are mutable and assignment (list_b = list_a) copies the reference, both variables point to the same list object in memory.

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:
Output

Why Does This Happen?

Numbers like 0.1 and 0.2 have infinite repeating representations in binary (similar to how 1/31/3 is 0.33333...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 (212^{-1}), 0.25 (222^{-2}), and 0.75 (21+222^{-1} + 2^{-2}) are sums of exact powers of 2. They can be represented perfectly in binary.
Output

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:
Output
(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:
Decimal(0.1) inherits the imprecise representation of the float 0.1, whereas Decimal('0.1') represents exactly 0.1.

Exercise 2

Will the expression 0.125 + 0.125 == 0.25 be True or False?
0.125 (232^{-3}) and 0.25 (222^{-2}) can be represented exactly in binary, so no rounding error occurs.

String Indexing and Slicing

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

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

Output

Extracting a Portion of a String

Output

Omitting Start or Stop

Output

Using Step

Output

Reversing a String

A negative step traverses the string from right to left.
Output

Practical Example

Extract the file extension.
Output

Exercise 1

Extract "Python" from the following string.

Exercise 2

Reverse the following string using slicing.

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.

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.

Joining Lists (.join())

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

3. Replacing Substrings (.replace())

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

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

Useful for filtering filenames, URLs, or protocols.

Exercise 3

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

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.

Python Nuances

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

Short-Circuit Evaluation

Logical operators don’t always evaluate every expression.

Using or

Suppose a user doesn’t enter a name.
The same logic can be written more concisely.
Output
If a username exists,
Output

Using and

Suppose a user must be logged in before displaying a welcome message.
Python allows this shorter form.
If logged_in is False, the second expression is never evaluated.

Exercise 1

Print "Anonymous" whenever name is an empty string.

Exercise 2

Print "Access Granted" only when is_admin is True.

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):

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.

Exercise 2

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

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

Traditional if-else blocks:
Can be written cleanly as:
Output

Chained Comparisons

Many range checks require two comparisons. Traditional approach:
Python provides a cleaner syntax.
This syntax is easier to read and is preferred in Python. Another example:
Checking whether a character is lowercase.

Exercise 1

Check whether temperature lies between 20 and 35.

Exercise 2

Check whether a character is an uppercase alphabet.

Multiple Assignment and Variable Swapping

Multiple Assignment

Instead of assigning each variable separately,
Python allows

Assigning Multiple Values

Swapping Variables

Traditional approach:
Python provides a much cleaner solution.
Output

Exercise 1

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

Exercise 2

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

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.
Output
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.
Output
This is one of the most common real-world uses of for-else.

while-else

The else block also works with while loops.
Output

Exercise 1

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

Exercise 2

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

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.

Positional Arguments

Positional arguments are matched with function parameters based on their position.
Output
Here,
  • "Alice" is assigned to name
  • "Hyderabad" is assigned to city
The order of the arguments is important.
Output

Exercise 1

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

Exercise 2

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

Keyword Arguments

Keyword arguments pass values using parameter names instead of their positions.
Output
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.

Exercise 2

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

Mixing Positional and Keyword Arguments

Positional and keyword arguments can be used together.
Output

Rule

All positional arguments must appear before keyword arguments. ✔️ Correct
❌ Incorrect
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.

Exercise 2

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

Positional-only Parameters (/)

Python allows certain parameters to be passed only by position. The / symbol separates positional-only parameters from the remaining parameters.
Valid
Invalid
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,
The function works correctly regardless of the internal parameter names.

Exercise 1

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

Exercise 2

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

Keyword-only Parameters (*)

Parameters appearing after * must always be passed using keyword arguments.
Valid
Invalid
The second call raises a TypeError.

Why Use Keyword-only Parameters?

Keyword arguments improve readability, especially when a function has several optional settings.
The purpose of each value is immediately clear.

Combining Both

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

Exercise 1

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

Exercise 2

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

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.

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.
Output
A practical example:
Output

Exercise 1

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

Exercise 2

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

Packing Keyword Arguments (**kwargs)

**kwargs collects keyword arguments into a dictionary.
Output
Access individual values.
Output

Exercise 1

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

Exercise 2

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

Unpacking Positional Arguments (*)

Packing collects values. Unpacking performs the opposite operation. Instead of passing arguments one by one,
the values can be stored in a tuple and unpacked.
The * operator expands the tuple into individual arguments.

Exercise 1

Call a function by unpacking a tuple.

Exercise 2

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

Unpacking Keyword Arguments (**)

A dictionary can also be unpacked into keyword arguments.
The dictionary keys must match the parameter names.

Exercise 1

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

Exercise 2

Call a function by unpacking a dictionary containing employee details.

Extended Unpacking

Python allows collecting the remaining values using *.
Output
This is useful when only the first and last elements are important. Another example.

Exercise 1

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

Exercise 2

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

Combining Everything

Python allows all parameter types to be used together.
Output
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.

Exercise 2

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

Practice

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

Follow-Along Practice

Practice string slicing, short-circuit logic, chained comparisons, variable swapping, loop-else clauses, and packing/unpacking positional and keyword arguments.💻 VS Code | 🚀 Colab | 📥 Download

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