1. The Peril of Shallow Copies: Understanding References 🧠
Description: When you assign one list to another using =, you're not creating a new list; you're creating a new reference to the same list object. Modifications through one reference will affect the other. ⚠️
Correct Usage: Create a true copy to ensure independence. ✅
original = [1, 2, [3, 4]]
copy_slice = original[:] # or original.copy() for shallow copy
copy_slice[2][0] = 99
print(f"Correct (original): {original}") # Output: [1, 2, [99, 4]] (still shallow)
import copy
deep_copy = copy.deepcopy(original) # for nested structures
deep_copy[2][0] = 100
print(f"Correct (original after deep_copy): {original}") # Output: [1, 2, [99, 4]]
Incorrect Usage: Direct assignment creates an alias. ❌
list_a = [1, 2, 3]
list_b = list_a # list_b now refers to the SAME object as list_a
list_b.append(4)
print(f"Incorrect (list_a): {list_a}") # Output: [1, 2, 3, 4]
Brief Explanation: = assigns a reference. Use slicing [:] or .copy() for shallow copies, and copy.deepcopy() for independent copies of nested lists. 🔑
---
2. Modifying a List During Iteration 🔄
Description: Modifying a list while iterating over it (e.g., removing elements) can lead to unpredictable behavior because the list's length and indices change during the loop. ⚠️
Correct Usage: Iterate over a copy of the list or use a list comprehension. ✅
my_numbers = [1, 2, 3, 4, 5, 6]
new_numbers = [num for num in my_numbers if num % 2 == 0]
print(f"Correct: {new_numbers}") # Output: [2, 4, 6]
# Alternatively, iterate over a copy for removals:
# for item in my_numbers[:]: ...
Incorrect Usage: Modifying the original list directly while iterating. ❌
nums = [1, 2, 3, 4, 5]
for num in nums:
if num % 2 != 0:
nums.remove(num) # This will skip elements or raise errors
print(f"Incorrect: {nums}") # Output: [2, 4] (missed 3, removed 1 and 5)
Brief Explanation: Changing the list's size or order mid-iteration confuses the loop's internal index. Use list comprehensions or iterate over a copy to ensure stable iteration. 🛡️
---
3. Append vs. Extend for Adding Elements ➕
Description: append() adds a single element (which can be another list) to the end of the list. extend() iterates over an iterable and adds each of its elements to the list.
Correct Usage: Choose based on whether you want to add one item or multiple items individually. ✅
list1 = [1, 2]
list1.append([3, 4]) # Adds the list [3, 4] as one element
print(f"Correct (append list): {list1}") # Output: [1, 2, [3, 4]]
list2 = [1, 2]
list2.extend([3, 4]) # Adds 3, then 4 as separate elements
print(f"Correct (extend list): {list2}") # Output: [1, 2, 3, 4]
Incorrect Usage: Using append() when you want to flatten an iterable into the current list. ❌
data = [1, 2]
extra_data = [3, 4]
data.append(extra_data) # Appends the entire extra_data list as a single element
print(f"Incorrect: {data}") # Output: [1, 2, [3, 4]]
Brief Explanation: append() adds one item; extend() adds items from an iterable one by one. 🧩
---
4. Efficient Membership Testing 🔍
Description: Checking if an item is present in a list is a common operation. Python provides an optimized in operator for this, which is generally more efficient and readable than manual iteration.
Correct Usage: Use the in operator. ✅
student_ids = [101, 105, 112, 115]
if 105 in student_ids:
print("Correct: Student 105 is enrolled.")
Incorrect Usage: Manually looping to find an item. ❌
