CodingNic

Python Lists

List Basics

Python Lists 20 min read

List Basics

Objectives

By the end of this chapter, you should be able to:

  • Define what a list is in Python
  • Access and reassign values in a list
  • Use common built-in list methods
  • Copy or reverse a list using slices
  • Explain the difference between a shallow copy and a deep copy

💡 Why this matters: Almost every real program deals with a collection of things. Lists are Python’s default way to represent that, and you’ll use them constantly.

What Is a List?

A list is an ordered collection of elements. It can hold as many elements as you want, and they don’t all need to share a type:

python
number_list = [1, 2, 3, 4, 5]
string_list = ["a", "b", "c", "d"]
kitchen_sink_list = [4, "yo", None, False, True, ["another", "list"]]

Accessing and Reassigning Elements

Lists use bracket notation and a zero-based index: the first element is index 0:

python
my_list = ["a", 1, True]
my_list[0]  # "a"
my_list[2]  # True
my_list[3]  # IndexError: there's no index 3 here

Reassigning an element works the same way, with =:

python
my_list = ["a", 1, True]
my_list[2] = False
my_list  # ["a", 1, False]

Built-In List Methods

Method Does
.append(x) Adds x to the end of the list
.clear() Removes every element
.copy() Returns a new, independent list with the same elements
.count(x) Counts how many times x appears
.extend(other_list) Appends another list’s elements, flattened in (not nested)
.index(x) Returns the index of the first match; raises ValueError if not found
.insert(i, x) Inserts x at index i
.pop(i) Removes and returns the element at index i (last element if i is omitted)
.remove(x) Removes the first occurrence of x; raises ValueError if not found
.reverse() Reverses the list in place
.sort() Sorts the list in place

Here’s one example touching most of the table above, run in order on the same list:

python
fruits = ["banana", "apple", "cherry"]
fruits.append("date")      # add "date" to the end
fruits.insert(1, "kiwi")   # insert "kiwi" at index 1
fruits.index("cherry")     # 3 - the position of "cherry"
fruits.count("apple")      # 1 - "apple" appears once
fruits.sort()               # sort in place, alphabetically
fruits
# ['apple', 'banana', 'cherry', 'date', 'kiwi']

fruits.reverse()
fruits
# ['kiwi', 'date', 'cherry', 'banana', 'apple']

fruits.pop()               # removes and returns "apple", the last element
fruits.remove("kiwi")      # removes the first "kiwi" it finds
fruits
# ['date', 'cherry', 'banana']

fruits.clear()
fruits  # []

Two methods worth a closer look:

.copy() gives you a genuinely independent list: changes to the copy don’t affect the original:

python
original = [2, 3, 4]
duplicate = original.copy()
duplicate.remove(3)
duplicate  # [2, 4]
original   # [2, 3, 4] (untouched)

.extend() flattens the list you pass in, where .append() would nest it:

python
l = [1, 2, 3]
l.append([4])
l  # [1, 2, 3, [4]]

l.extend([5, 6, 7])
l  # [1, 2, 3, [4], 5, 6, 7]

.pop() and .remove() both raise an error under the wrong conditions: .pop() on an empty list raises IndexError, and .remove() raises ValueError if the value isn’t there at all.

python
empty = []
empty.pop()         # IndexError: pop from empty list
empty.remove("x")   # ValueError: list.remove(x): x not in list

Copying and Reversing with Slices

A slice grabs a portion of a list (or string), using list[start:end] or list[start:end:step]:

python
first_list = [1, 2, 3, 4, 5, 6]

first_list[0:1]   # [1]
first_list[1:]    # [2, 3, 4, 5, 6] (no end means "to the end")
first_list[:3]    # [1, 2, 3] (no start means "from the beginning")
first_list[-1]    # 6 (negative indexes count from the end)
first_list[-2:]   # [5, 6]
first_list[::-1]  # [6, 5, 4, 3, 2, 1] (a reversed copy)
first_list[::-2]  # [6, 4, 2] (reversed, stepping by two)

list[:] is a common shorthand for “copy the whole list.”

Shallow Copies vs. Deep Copies

new_list = original does not copy anything: it just gives the same list a second name. Both names point at the same object in memory, so a change through either one shows up through the other:

python
original = [1, 2, 3]
alias = original
alias.append(4)

original  # [1, 2, 3, 4] (changed too, since alias IS original)

.copy() (and list[:]) fixes that for a flat list, but only one level deep. If a list contains other mutable objects, like nested lists, both the original and the copy still share those inner objects:

python
import copy

original = [1, 2, [3, 4]]
shallow = original.copy()

shallow[0] = 99          # doesn't affect original (top-level values are independent)
shallow[2].append(5)     # DOES affect original (the nested list is shared)

original  # [1, 2, [3, 4, 5]]

For a fully independent copy, including every nested list, dict, or other mutable object inside it, use copy.deepcopy() instead:

python
import copy

original = [1, 2, [3, 4]]
deep = copy.deepcopy(original)

deep[2].append(5)
original  # [1, 2, [3, 4]] (completely untouched this time)

Reach for .copy()/list[:] for a flat list of simple values, and copy.deepcopy() whenever your list contains other lists, dicts, or objects you don’t want shared.

Try It

  1. Build a list of five items and practice .append(), .remove(), .pop(), and .sort() on it.
  2. Use a slice to create a reversed copy of a list without mutating the original.
  3. Predict what original.copy() gives you versus new_list = original, then check by modifying one and seeing what happens to the other.
  4. Build a list containing a nested list, make a shallow copy with .copy(), modify the nested list through the copy, and confirm the original changed too. Then repeat with copy.deepcopy() and confirm it didn’t.

Recap

  • Lists are ordered, mutable, zero-indexed collections that can hold any mix of types.
  • .append()/.insert() add elements; .pop()/.remove()/.clear() take them away; .sort()/.reverse() reorder in place; .copy()/.index()/.count() read without mutating.
  • Slices (list[start:end:step]) grab a portion, copy (list[:]), or reverse (list[::-1]) a list.
  • new_list = original doesn’t copy anything: both names share the same list. .copy()/list[:] make a shallow copy (nested objects are still shared); copy.deepcopy() makes every level independent.

Next lesson: looping over lists efficiently, and list comprehension, one of Python’s most useful shortcuts.