1. What are Lists? The Basics
A list is an ordered, mutable (changeable) collection of items. Key characteristics:
-
Ordered: Items have a defined order, and that order will not change. You can access items by their position (index).
-
Mutable: Unlike strings, lists can be changed after they are created. You can add, remove, or modify elements.
-
Allows Duplicates: You can have multiple items with the same value.
-
Can Hold Mixed Data Types: A single list can contain integers, strings, floats, Booleans, and even other lists!
Syntax: Lists are created using square brackets `[]`, with items separated by commas.
# An empty list
empty_list = []
# List of integers
numbers = [1, 2, 3, 4, 5]
# List of strings (UdaanPath courses)
courses = ["Python Basics", "Web Dev", "Data Science", "Python Basics"] # Duplicates allowed
# List with mixed data types
mixed_data = ["Alice", 25, True, 175.5]
print(f"Empty list: {empty_list}")
print(f"Numbers: {numbers}")
print(f"Courses: {courses}")
print(f"Mixed data: {mixed_data}")
print(f"Type of 'courses': {type(courses)}")
Empty list: []
Numbers: [1, 2, 3, 4, 5]
Courses: ['Python Basics', 'Web Dev', 'Data Science', 'Python Basics']
Mixed data: ['Alice', 25, True, 175.5]
Type of 'courses': <class 'list'>
2. Accessing Elements: Indexing and Slicing
Accessing individual items or portions of a list works very similarly to strings, using indexing and slicing.
Indexing:
-
Positive Indices: Start from `0` for the first element.
-
Negative Indices: Start from `-1` for the last element.
udaan_team = ["Aditya", "Bhavna", "Chirag", "Deepika"]
# Indices: 0 1 2 3
# Neg.Idx: -4 -3 -2 -1
print(f"First member: {udaan_team[0]}")
print(f"Third member: {udaan_team[2]}")
print(f"Last member: {udaan_team[-1]}")
print(f"Second to last member: {udaan_team[-2]}")
First member: Aditya
Third member: Chirag
Last member: Deepika
Second to last member: Chirag
Slicing:
Syntax: `list[start:end:step]`
- `start`: Inclusive. Default 0.
- `end`: Exclusive. Default end of list.
- `step`: How many elements to jump. Default 1.
prime_numbers = [2, 3, 5, 7, 11, 13, 17, 19]
print(f"First 3 primes: {prime_numbers[0:3]}") # or prime_numbers[:3]
print(f"Primes from index 4: {prime_numbers[4:]}")
print(f"Primes from index 2 to 5: {prime_numbers[2:6]}")
print(f"Every second prime: {prime_numbers[::2]}")
print(f"Reversed list: {prime_numbers[::-1]}")
First 3 primes: [2, 3, 5]
Primes from index 4: [11, 13, 17, 19]
Primes from index 2 to 5: [5, 7, 11, 13]
Every second prime: [2, 5, 11, 17]
Reversed list: [19, 17, 13, 11, 7, 5, 3, 2]
3. Modifying Lists: Adding and Changing Elements
Lists are mutable, meaning you can change their content after creation.
Changing Elements:
Assign a new value to an element at a specific index.
tasks = ["Buy groceries", "Do laundry", "Pay bills"]
print(f"Original tasks: {tasks}")
tasks[1] = "Call mechanic" # Modify element at index 1
print(f"Modified tasks: {tasks}")
Original tasks: ['Buy groceries', 'Do laundry', 'Pay bills']
Modified tasks: ['Buy groceries', 'Call mechanic', 'Pay bills']
Adding Elements:
-
.append(item): Adds an item to the end of the list.
-
.insert(index, item): Inserts an item at a specified index.
-
.extend(iterable): Appends all items from an iterable (like another list) to the end of the current list.
-
`+` operator: Concatenates two lists to create a new list (doesn't modify existing lists).
my_list = ["apple", "banana"]
print(f"Initial list: {my_list}")
my_list.append("cherry") # Add to end
print(f"After append: {my_list}")
my_list.insert(1, "orange") # Insert at index 1
print(f"After insert: {my_list}")
other_fruits = ["grape", "kiwi"]
my_list.extend(other_fruits) # Extend with another list
print(f"After extend: {my_list}")
new_combined_list = my_list + ["mango", "peach"] # Create a NEW list
print(f"New combined list (using +): {new_combined_list}")
print(f"Original 'my_list' (unchanged by +): {my_list}") # my_list is still the same
Initial list: ['apple', 'banana']
After append: ['apple', 'banana', 'cherry']
After insert: ['apple', 'orange', 'banana', 'cherry']
After extend: ['apple', 'orange', 'banana', 'cherry', 'grape', 'kiwi']
New combined list (using +): ['apple', 'orange', 'banana', 'cherry', 'grape', 'kiwi', 'mango', 'peach']
Original 'my_list' (unchanged by +): ['apple', 'orange', 'banana', 'cherry', 'grape', 'kiwi']
4. Removing Elements from Lists
Just as easily as you add, you can remove items from lists.
-
del list[index]: Deletes an item at a specific index. Can also delete slices (`del my_list[1:3]`).
-
.pop([index]): Removes and returns the item at the given index. If no index is specified, it removes and returns the last item.
-
.remove(value): Removes the first occurrence of the specified value. Raises a `ValueError` if the value is not found.
-
.clear(): Removes all items from the list, making it empty.
student_grades = [90, 75, 88, 60, 95]
print(f"Initial grades: {student_grades}")
del student_grades[3] # Remove grade at index 3 (60)
print(f"After del index 3: {student_grades}")
popped_grade = student_grades.pop() # Remove and get the last item (95)
print(f"After pop (last): {student_grades}, Popped: {popped_grade}")
popped_specific = student_grades.pop(0) # Remove and get item at index 0 (90)
print(f"After pop (index 0): {student_grades}, Popped: {popped_specific}")
student_names = ["Alice", "Bob", "Charlie", "Bob"]
print(f"Initial names: {student_names}")
student_names.remove("Bob") # Removes the FIRST "Bob"
print(f"After remove 'Bob': {student_names}")
try:
student_names.remove("David") # Value not in list
except ValueError as e:
print(f"Error: {e}")
my_temp_list = [10, 20, 30]
my_temp_list.clear()
print(f"After clear: {my_temp_list}")
Initial grades: [90, 75, 88, 60, 95]
After del index 3: [90, 75, 88, 95]
After pop (last): [90, 75, 88], Popped: 95
After pop (index 0): [75, 88], Popped: 90
Initial names: ['Alice', 'Bob', 'Charlie', 'Bob']
After remove 'Bob': ['Alice', 'Charlie', 'Bob']
Error: list.remove(x): x not in list
After clear: []
Interview Tip: Differentiate `del`, `.pop()`, and `.remove()`. `del` is a statement for deleting by index/slice. `.pop()` removes by index (default last) and returns the value. `.remove()` removes by value.
5. Other Useful List Methods
-
len(list): (Built-in function) Returns the number of items in the list.
-
.count(value): Returns the number of times a specified value appears in the list.
-
.index(value, [start, end]): Returns the index of the first occurrence of the specified value. Raises `ValueError` if not found. Optional `start` and `end` arguments can limit the search.
-
.sort(): Sorts the list in-place (modifies the original list). Default is ascending.
-
sorted(list): (Built-in function) Returns a new sorted list, leaving the original list unchanged.
-
.reverse(): Reverses the order of elements in-place.
-
.copy(): Returns a shallow copy of the list. Important for avoiding unexpected modifications when you want a separate, independent copy.
data = [10, 5, 20, 5, 15, 5]
print(f"Data: {data}")
print(f"Length of data: {len(data)}")
print(f"Count of 5s: {data.count(5)}")
print(f"Index of first 20: {data.index(20)}")
# Sorting
numbers_to_sort = [3, 1, 4, 1, 5, 9]
print(f"Original for sort: {numbers_to_sort}")
numbers_to_sort.sort() # In-place sort
print(f"After .sort(): {numbers_to_sort}")
another_list = [7, 2, 8, 4]
sorted_new_list = sorted(another_list) # Returns a NEW sorted list
print(f"Original for sorted(): {another_list}")
print(f"After sorted(): {sorted_new_list}")
# Reversing
my_chars = ['a', 'b', 'c']
print(f"Original for reverse: {my_chars}")
my_chars.reverse() # In-place reverse
print(f"After .reverse(): {my_chars}")
# Copying
original_scores = [85, 90, 78]
copied_scores = original_scores.copy() # Creates a shallow copy
copied_scores[0] = 100 # Modifying copy doesn't affect original
print(f"Original scores: {original_scores}")
print(f"Copied scores: {copied_scores}")
Data: [10, 5, 20, 5, 15, 5]
Length of data: 6
Count of 5s: 3
Index of first 20: 2
Original for sort: [3, 1, 4, 1, 5, 9]
After .sort(): [1, 1, 3, 4, 5, 9]
Original for sorted(): [7, 2, 8, 4]
After sorted(): [2, 4, 7, 8]
Original for reverse: ['a', 'b', 'c']
After .reverse(): ['c', 'b', 'a']
Original scores: [85, 90, 78]
Copied scores: [100, 90, 78]
Deep vs. Shallow Copy: `.copy()` creates a "shallow copy". If your list contains mutable objects (like other lists), modifying those nested objects in the copy *will* affect the original. For truly independent copies of complex lists, you'd use `copy.deepcopy()` from the `copy` module. (More advanced topic).
6. Iterating Over Lists: Putting Loops to Work
Lists and `for` loops are a perfect match. You can easily iterate through each element.
Basic Iteration:
students = ["Rahul", "Priya", "Amit", "Sonia"]
print("Welcome messages for UdaanPath students:")
for student in students:
print(f"Hello, {student}! Welcome to UdaanPath.")
Welcome messages for UdaanPath students:
Hello, Rahul! Welcome to UdaanPath.
Hello, Priya! Welcome to UdaanPath.
Hello, Amit! Welcome to UdaanPath.
Hello, Sonia! Welcome to UdaanPath.
Iterating with Index (`enumerate()`):
If you need both the item and its index, use the built-in enumerate() function.
products = ["Laptop", "Mouse", "Keyboard"]
print("UdaanPath Inventory:")
for index, product in enumerate(products):
print(f"Item {index + 1}: {product}")
UdaanPath Inventory:
Item 1: Laptop
Item 2: Mouse
Item 3: Keyboard
7. List Comprehensions: Concise List Creation
List comprehensions offer a powerful, concise, and readable way to create new lists based on existing iterables. They often replace `for` loops with `append()` calls.
Syntax: `[expression for item in iterable if condition]`
# Traditional way:
squares = []
for i in range(1, 6):
squares.append(i**2)
print(f"Squares (traditional): {squares}")
# Using List Comprehension:
squares_lc = [i**2 for i in range(1, 6)]
print(f"Squares (List Comp.): {squares_lc}")
# List comprehension with a condition (even numbers only)
even_numbers = [num for num in range(1, 11) if num % 2 == 0]
print(f"Even numbers (List Comp.): {even_numbers}")
# Converting strings to uppercase
words = ["udaan", "path", "python"]
uppercase_words = [word.upper() for word in words]
print(f"Uppercase words: {uppercase_words}")
Squares (traditional): [1, 4, 9, 16, 25]
Squares (List Comp.): [1, 4, 9, 16, 25]
Even numbers (List Comp.): [2, 4, 6, 8, 10]
Uppercase words: ['UDAAN', 'PATH', 'PYTHON']
Best Practice: Use list comprehensions when creating a new list by transforming or filtering an existing iterable. They are generally more efficient and Pythonic than explicit `for` loops for these tasks.