Skip to content

Latest commit

 

History

History
352 lines (251 loc) · 11.5 KB

File metadata and controls

352 lines (251 loc) · 11.5 KB

Python Lists — Ordered, Mutable Sequences

📜 Welcome to Sequences

Over the next few chapters you'll meet Python's three basic sequence types: lists, tuples, and ranges. They all store ordered collections of items — the differences are in mutability, syntax, and use case.

Big idea: a list is an ordered, mutable, zero-indexed sequence that can hold any mix of types — strings, numbers, booleans, even other lists. It's the workhorse data structure of Python.

🧩 The Three Defining Traits of a List

Trait Meaning
Ordered Items keep their insertion order. [1, 2, 3] is not the same as [3, 2, 1].
Mutable You can change, add, and remove items in place — without rebuilding the list.
Zero-indexed The first element is at index 0, the second at 1, and so on.

📝 Creating a List — Literal Syntax

Use square brackets with comma-separated values:

cities = ['Los Angeles', 'London', 'Tokyo']
numbers = [1, 2, 3, 4, 5]
mixed = ['Alice', 25, True, 3.14, None]
empty = []

Lists can mix types freely — Python doesn't restrict you to one type per list.

🎯 Indexing — Accessing Items

Use the index number inside square brackets:

cities = ['Los Angeles', 'London', 'Tokyo']
cities[0]  # 'Los Angeles'  ← first element (index 0!)
cities[1]  # 'London'
cities[2]  # 'Tokyo'

⬅️ Negative Indexing

Negative indices count from the end:

cities[-1]  # 'Tokyo'        ← last element
cities[-2]  # 'London'       ← second to last
cities[-3]  # 'Los Angeles'  ← third to last (first)
flowchart LR
	IDX0["0"] --- LA["'Los Angeles'"]
	IDX1["1"] --- LON["'London'"]
	IDX2["2"] --- TOK["'Tokyo'"]
	LA --- NEG3["-3"]
	LON --- NEG2["-2"]
	TOK --- NEG1["-1"]
Loading
💡

Memory hook: list[0] is the first item, not the second. list[-1] is always the last item, no matter the list size.

🏗️ The list() Constructor

Another way to create a list is to convert an existing iterable using list():

developer = 'Jessica'
list(developer)  # ['J', 'e', 's', 's', 'i', 'c', 'a']

list(range(5))   # [0, 1, 2, 3, 4]
list((1, 2, 3))  # [1, 2, 3]   (tuple → list)
list()           # []          (empty list)

An iterable is anything you can loop over one item at a time — strings, tuples, ranges, dictionaries, sets, files, etc. You'll meet loops formally soon.

📏 Length — len()

Get the number of items with the built-in len():

numbers = [1, 2, 3, 4, 5]
len(numbers)  # 5

len([])       # 0
len([[1, 2], [3, 4]])  # 2 (two nested lists)

✏️ Updating Items — Mutability in Action

Because lists are mutable, you can replace any element by its index:

programming_languages = ['Python', 'Java', 'C++', 'Rust']
programming_languages[0] = 'JavaScript'
print(programming_languages)
# ['JavaScript', 'Java', 'C++', 'Rust']

This edits the list in place — no new list is created. Compare with strings, which are immutable: s[0] = 'X' raises TypeError.

⚠️ Out-of-Range Indices → IndexError

If you use an index that doesn't exist, Python raises IndexError:

programming_languages = ['Python', 'Java', 'C++', 'Rust']
programming_languages[10] = 'JavaScript'
# IndexError: list assignment index out of range

Valid positive indices: 0 to len(list) - 1. Valid negative indices: -1 to -len(list).

🗑️ Removing Items — The del Keyword

developer = ['Jane Doe', 23, 'Python Developer']
del developer[1]
print(developer)  # ['Jane Doe', 'Python Developer']

del removes the element at the given index and shifts everything after it one position left. You'll meet other removal methods (remove(), pop()) in the next chapter.

🔍 Membership — The in Keyword

Check whether a value exists in a list with in — returns a boolean:

programming_languages = ['Python', 'Java', 'C++', 'Rust']

'Rust' in programming_languages         # True
'JavaScript' in programming_languages   # False
'JavaScript' not in programming_languages  # True

in compares using ==, so it works for strings, numbers, and any object with proper equality.

🪶 Nested Lists

Lists can contain other lists — great for tables, matrices, grouped data:

developer = ['Alice', 25, ['Python', 'Rust', 'C++']]

Access the inner list with one index, then drill in with another:

developer[2]      # ['Python', 'Rust', 'C++']
developer[2][0]   # 'Python'  ← first language
developer[2][1]   # 'Rust'    ← second language
developer[2][-1]  # 'C++'     ← last language

Think of each […] as stepping one level deeper into the nested structure.

flowchart TD
	DEV["developer"] --> A["[0] 'Alice'"]
	DEV --> AGE["[1] 25"]
	DEV --> LANGS["[2] [...]"]
	LANGS --> P["[0] 'Python'"]
	LANGS --> R["[1] 'Rust'"]
	LANGS --> C["[2] 'C++'"]
Loading

🎁 Unpacking — Assigning Many Variables at Once

Unpacking spreads list values into separate variables in one statement:

developer = ['Alice', 34, 'Rust Developer']
name, age, job = developer

print(name)  # 'Alice'
print(age)   # 34
print(job)   # 'Rust Developer'

The number of variables must match the number of items — or use * to capture the rest.

✯ Star (*) — Catch the Remainder

developer = ['Alice', 34, 'Rust Developer']
name, *rest = developer

print(name)  # 'Alice'
print(rest)  # [34, 'Rust Developer']

*rest collects everything else as a list. It can go at the start, middle, or end:

first, *middle, last = [1, 2, 3, 4, 5]
# first = 1, middle = [2, 3, 4], last = 5

*head, tail = [10, 20, 30]
# head = [10, 20], tail = 30

⚠️ Wrong Variable Count → ValueError

developer = ['Alice', 34, 'Rust Developer']
name, age, job, city = developer
# ValueError: not enough values to unpack (expected 4, got 3)

With *, this restriction relaxes — you only need at least as many fixed names as fixed values.

✂️ Slicing — list[start:stop:step]

Access a portion of a list with the slice operator : (just like strings):

desserts = ['Cake', 'Cookies', 'Ice Cream', 'Pie', 'Brownies']
desserts[1:4]  # ['Cookies', 'Ice Cream', 'Pie']
  • start — inclusive (default 0)
  • stopexclusive (default end)
  • step — stride (default 1)
desserts[:3]    # ['Cake', 'Cookies', 'Ice Cream']  ← first three
desserts[2:]    # ['Ice Cream', 'Pie', 'Brownies']  ← from index 2 to end
desserts[::2]   # ['Cake', 'Ice Cream', 'Brownies'] ← every other item
desserts[::-1]  # ['Brownies', 'Pie', 'Ice Cream', 'Cookies', 'Cake'] ← reversed!

🔢 Step Example — Extract Every Other Item

numbers = [1, 2, 3, 4, 5, 6]
numbers[1::2]  # [2, 4, 6]
  • Start at index 1 (the first even number 2).
  • Omit stop → go all the way to the end.
  • step = 2 → grab every second item.
💡

Slices never raise IndexError. Out-of-range stops are silently clipped: [1, 2, 3][:100] returns [1, 2, 3]. Use indexing for single items and slicing for ranges.

🔀 Indexing vs Slicing — Quick Comparison

flowchart TD
	WANT{"What do you want?"} --> ONE{"A single item?"}
	ONE -- Yes --> IDX["✅ Use indexing<br>list[i]<br>Returns: the element"]
	ONE -- No --> MANY["✅ Use slicing<br>list[a:b:c]<br>Returns: a new list"]
	IDX --> BOUNDS["⚠️ IndexError if out of range"]
	MANY --> SAFE["✅ No error — silently clipped"]
Loading

🧬 Putting It All Together — A Mini Walkthrough

developer = ['Alice', 34, ['Python', 'Rust', 'C++']]

# Read
print(developer[0])        # 'Alice'
print(developer[-1])       # ['Python', 'Rust', 'C++']
print(developer[-1][1])    # 'Rust'

# Update (mutable!)
developer[1] = 35
print(developer)           # ['Alice', 35, ['Python', 'Rust', 'C++']]

# Check membership
print('Alice' in developer)        # True
print('Python' in developer[-1])   # True

# Remove
del developer[1]
print(developer)           # ['Alice', ['Python', 'Rust', 'C++']]

# Unpack
name, langs = developer
print(name)                # 'Alice'
print(langs)               # ['Python', 'Rust', 'C++']

# Slice
print(langs[:2])           # ['Python', 'Rust']
print(langs[::-1])         # ['C++', 'Rust', 'Python']

🎯 Best Practices

  • ✅ Use a list when order matters and you may need to modify the collection.
  • ✅ Use len() before risky indexing if the list might be empty.
  • ✅ Prefer in for membership checks — cleaner than manual loops.
  • ✅ Use negative indices (-1) for "last item" instead of list[len(list) - 1].
  • ✅ Use unpacking to make multi-value assignments expressive: x, y, z = point reads better than x = point[0]; y = point[1]; z = point[2].
  • ✅ Use slicing to take portions — it's safer than indexing because it never raises out-of-range errors.
  • ✅ For very deeply nested data, consider giving the inner lists names (or using dictionaries) for readability.

⚠️ Common Mistakes to Avoid

  • ❌ Counting from 1 instead of 0list[1] is the second element, not the first.
  • ❌ Trying to assign to an out-of-range index → IndexError. Use append() to grow a list.
  • ❌ Confusing del list[i] with list.remove(value)del is by index, remove() is by value.
  • ❌ Unpacking with the wrong number of variables → ValueError. Use *rest for variable-length unpacking.
  • ❌ Slicing with a stop lower than start but no negative step → you get an empty list silently.
  • ❌ Modifying a list while iterating over it — subtle bugs; covered in the loops chapter.
  • ❌ Treating lists as if they were immutable like strings — list[i] = x works; str[i] = x errors.

❓ Quick Self-Check

  • Which of the following is true about lists?They are zero-based indexed.
  • Which is the correct way to access the second element in cities = ['Los Angeles', 'London', 'Tokyo']?cities[1]
  • Which is the correct way to access the second element from the end of numbers = [1, 2, 3, 4, 5, 6]?numbers[-2]

✅ Key Takeaways

  • A list is an ordered, mutable, zero-indexed sequence — the workhorse data structure of Python.
  • Create lists with [...] literals or the list() constructor (converts any iterable).
  • list[0] is the first item; list[-1] is the last.
  • len(list) → number of items. x in list → boolean membership check.
  • Lists are mutable: list[i] = value updates in place. Out-of-range → IndexError.
  • del list[i] removes an item by index.
  • Lists can be nested — chain indices to drill deeper: data[2][1].
  • Unpacking assigns many variables at once: a, b, c = list. Use *rest to catch the remainder.
  • Slicing list[start:stop:step] returns a new list — never raises out-of-range errors.
  • Next up: list methods — the built-in tools that make lists really sing.

➡️

Next chapter → Python List Methods — append, pop, sort & More

Lists are powerful because of their methods. Meet append, extend, insert, remove, pop, clear, sort, reverse, and index — plus the crucial sort() vs sorted() distinction.