Lists in Python
By the Syncfusion DevTutorials team · Last updated
Creating, indexing and slicing lists, updating and removing items, sorting with sort() and sorted(), shallow against deep copying, and every list method.
Think about a shopping list on a piece of paper.
Items are written one under another, in the order you thought of them. You can add to the bottom, cross something out, squeeze a new item between two others, or read the third one without reading the first two.
That is a list: many values, in order, and changeable.
Here is a list of marks in action:
A list of marks
marks = [78, 85, 60]
print(marks)
print(marks[0])
print(len(marks))
marks.append(92)
print(marks)
Lists are the collection Python programs use most. Nearly everything you did with arrays applies here, with fewer rules and more methods.
A list holds many values in order, and you can change it after you make it.
What You Will Learn
In this lesson, you will learn:
- How to make a list, and what makes lists different from the other collections
- How to reach one item, or a run of them
- How to change, add and remove items
- How to sort, and the difference between
sort()andsorted() - Why copying a list is harder than it looks
- Every list method, and the built-ins that work on lists
By the end of this lesson, you will be able to create lists, reach items with indexes and slices, add, change and remove items, sort a list with sort() and sorted(), copy a list without surprises, and pick the right list method or built-in for each job.
Python Lists
Introduction to Lists
A list is an ordered, changeable collection. Each item has a position, starting at 0.
Creating Lists
There are several ways to make a list, and all of them are common:
Four ways to make a list
marks = [78, 85, 60]
empty = []
also_empty = list()
from_text = list("Asha")
print(marks)
print(empty, also_empty)
print(from_text)
list() turns anything you can loop over into a list, which is often how one gets built:
Building a list from something else
print(list(range(5)))
print(list("Asha"))
print(list((78, 85)))
print([mark * 2 for mark in [78, 85]])
The last line is a list comprehension — a loop written inside the brackets. It is the usual way to build a list from another one.
List Characteristics
| Characteristic | What it means |
|---|---|
| Ordered | items keep the order you put them in |
| Indexed | the first item is at 0 |
| Changeable | you can replace, add and remove items |
| Duplicates allowed | the same value can appear many times |
| Any type | items need not match |
| Nestable | a list can hold other lists |
Duplicates and nesting are both fine
marks = [78, 78, 85]
nested = [[78, 85], [60, 92]]
print(marks, marks.count(78))
print(nested)
print(nested[0], nested[0][1])
Heterogeneous Lists
A list can hold different types at once. This is where it differs from the array module.
Mixed types in one list
record = ["Asha", 13, 78.5, True, None, [78, 85]]
for item in record:
print(type(item).__name__, "->", item)
Allowed, and usually a bad idea. A list whose items vary means every loop over it has to check what it has. A dictionary or a class says what each piece is.
Mutable Nature of Lists
Mutable means changeable in place. A list is; a string and a tuple are not.
A list changes in place; a string cannot
marks = [78, 85]
marks[0] = 90
print(marks)
name = "Asha"
try:
name[0] = "M"
except TypeError as error:
print("A string refuses:", error)
That one difference drives most of what follows, especially copying.
Accessing List Elements
Now that you know how to make a list, the next question is how to reach what is inside one.
List Indexing
Positive Indexing
Count from the front, starting at 0:
Counting from the front
marks = [78, 85, 60, 92]
print(marks[0])
print(marks[2])
print(marks[len(marks) - 1])
One past the end
marks = [78, 85]
print(marks[2])
Negative Indexing
You can also count from the back, using a minus sign:
Counting from the back
marks = [78, 85, 60, 92]
print(marks[-1])
print(marks[-2])
marks[-1] is the clearest way to say "the last one".
List Slicing
marks[start:stop:step]
A slice gives back a new list, from start up to but not including stop.
Taking a run of items
marks = [78, 85, 60, 92, 45]
print(marks[1:3])
print(marks[:3])
print(marks[2:])
print(marks[-2:])
print(marks[:])
Slice with Steps
Every second item, and backwards
marks = [78, 85, 60, 92, 45]
print(marks[::2])
print(marks[1::2])
print(marks[::-1])
[::-1] is the usual shorthand for a reversed copy.
Accessing Multiple Elements
A slice can also be assigned to, which replaces that run:
Replacing a run of items
marks = [78, 85, 60, 92]
marks[1:3] = [100, 200]
print(marks)
marks[1:3] = [1]
print(marks)
The replacement need not be the same length, so the list can shrink or grow.
A slice never raises, even when the numbers are out of range:
A slice out of range is simply empty
marks = [78, 85]
print(marks[5:10])
print(marks[0:100])
Updating Lists
You can reach any item now, so here is how to change one, or a whole run of them.
Modifying List Elements
Change a single item by assigning to its position:
Changing one item
marks = [78, 85, 60]
marks[1] = 90
print(marks)
Updating Multiple Elements
Changing several at once
marks = [78, 85, 60, 92]
marks[0:2] = [10, 20]
print(marks)
marks = [mark + 5 for mark in marks]
print(marks)
The second form builds a new list. To change the original in place, loop over the positions:
Changing every item in place
marks = [78, 85, 60]
for position in range(len(marks)):
marks[position] += 5
print(marks)
Adding Elements with append()
append() adds one item to the end.
append
marks = [78, 85]
marks.append(60)
marks.append([92, 45])
print(marks)
print(len(marks))
The list given to the second append went in as a single item. extend() is what adds each one.
Adding Elements with insert()
insert() puts an item at a position you choose instead of the end:
insert
marks = [78, 60]
marks.insert(1, 85)
marks.insert(0, 50)
print(marks)
Removing List Elements
Adding is only half of changing a list; here is how to take items out.
Using del
del removes by position and gives nothing back. It can also take a slice.
del
marks = [78, 85, 60, 92]
del marks[0]
print(marks)
del marks[0:2]
print(marks)
Using remove()
remove() deletes the first item equal to a value.
remove
marks = [78, 85, 60, 85]
marks.remove(85)
print(marks)
Removing something that is not there
marks = [78, 85]
marks.remove(99)
Using pop()
pop() removes by position and hands the item back.
pop
marks = [78, 85, 60]
print(marks.pop())
print(marks.pop(0))
print(marks)
Clearing a List
Three ways to empty a list
a = [78, 85]
a.clear()
print(a)
b = [78, 85]
del b[:]
print(b)
c = [78, 85]
c = []
print(c)
The first two empty the existing list. The third makes a new empty list and points the name at it, which matters when something else is holding the old one:
Why the difference matters
original = [78, 85]
other_name = original
original.clear()
print("After clear(), the other name sees:", other_name)
original = [78, 85]
other_name = original
original = []
print("After reassigning, the other name sees:", other_name)
List Operations
Concatenation (+)
Joining two lists
term1 = [78, 85]
term2 = [60, 92]
print(term1 + term2)
print(term1)
+ builds a new list and leaves both originals alone.
Repetition (*)
Repeating a list
print([0] * 5)
print([78, 85] * 2)
[0] * 5 is the usual way to make a list of a known size. Be careful with nested lists:
A repeated inner list is shared
grid = [[0] * 3] * 2
print(grid)
grid[0][0] = 99
print(grid)
Both rows changed, because * 2 repeated the same inner list twice. Build them separately:
Each row built on its own
grid = [[0] * 3 for _ in range(2)]
grid[0][0] = 99
print(grid)
Membership Operators
in and not in
marks = [78, 85, 60]
print(78 in marks)
print(99 in marks)
print(99 not in marks)
Iterating Through Lists
Three ways to loop
marks = [78, 85, 60]
for mark in marks:
print(mark)
for position, mark in enumerate(marks, start=1):
print(position, mark)
for subject, mark in zip(["Maths", "Science", "English"], marks):
print(subject, mark)
Never change a list's length while looping over it. The positions shift under the loop:
Removing while looping skips items
marks = [20, 15, 85, 60]
for mark in marks:
if mark < 35:
marks.remove(mark)
print(marks)
15 survived. When 20 was removed, everything shifted left, and the loop's next step skipped straight past it.
Build a new list instead
marks = [20, 15, 85, 60]
marks = [mark for mark in marks if mark >= 35]
print(marks)
Never add to or remove from a list while looping over it. Build a new list with a comprehension, or loop over a copy with for mark in marks[:].
Sorting Lists
Sorting a list wrong — or copying one wrong — causes bugs that look impossible. Sorting first:
Sorting with sort()
sort() sorts the list in place and returns None.
sort changes the list
marks = [78, 20, 85]
result = marks.sort()
print("The list now:", marks)
print("What sort() returned:", result)
marks = marks.sort() would leave you holding None.
Sorting with sorted()
sorted() returns a new sorted list and leaves the original alone.
sorted leaves the original
marks = [78, 20, 85]
ordered = sorted(marks)
print("New list:", ordered)
print("Original:", marks)
sorted() works on anything you can loop over, not just lists:
sorted works on more than lists
print(sorted("Asha"))
print(sorted((78, 20)))
print(sorted({"maths": 78, "science": 85}))
Ascending Order
Ascending is the default.
Ascending
print(sorted([78, 20, 85]))
print(sorted(["Science", "Maths", "English"]))
Descending Order and the reverse Parameter
reverse
marks = [78, 20, 85]
print(sorted(marks, reverse=True))
marks.sort(reverse=True)
print(marks)
reverse=True sorts the other way. list.reverse() is different — it flips the current order without sorting:
reverse() is not a descending sort
marks = [78, 20, 85]
marks.reverse()
print("reverse():", marks)
marks = [78, 20, 85]
marks.sort(reverse=True)
print("sort(reverse=True):", marks)
The key Parameter
key takes a function, applied to each item to decide what to sort by.
Sorting by something other than the value
subjects = ["Science", "maths", "English"]
print(sorted(subjects))
print(sorted(subjects, key=str.lower))
print(sorted(subjects, key=len))
The plain sort put capitals first, because capital letters come before small ones. key=str.lower compares them fairly.
Sorting records by one field
students = [("Asha", 78), ("Ravi", 85), ("Meera", 60)]
print(sorted(students))
print(sorted(students, key=lambda student: student[1]))
print(sorted(students, key=lambda student: student[1], reverse=True))
lambda student: student[1] is a small nameless function meaning "use the second item". Without a key, tuples sort by their first item.
Copying Lists
One idea left, and it saves hours of debugging: what happens when you assign a list to a second name.
Why Copying Matters
A list name holds a reference. Assigning it does not make a copy.
Assignment does not copy
original = [78, 85]
other = original
other.append(60)
print("original:", original)
print("other: ", other)
print("Same object?", original is other)
One list, two names. This surprises everyone once, and causes bugs that look impossible.
Assignment vs Copy
Four ways to actually copy
original = [78, 85]
a = original.copy()
b = original[:]
c = list(original)
import copy
d = copy.copy(original)
for name, made in [("copy()", a), ("[:]", b), ("list()", c), ("copy.copy()", d)]:
print(f"{name:<12} equal: {made == original} same object: {made is original}")
All four give a new list with the same items.
Shallow Copy
All four are shallow: the new list is new, but the items inside are the same objects.
A shallow copy shares what is inside
original = [[78, 85], [60, 92]]
shallow = original.copy()
shallow[0].append(99)
print("original:", original)
print("shallow: ", shallow)
The outer list was copied; the inner lists were not. Changing one through either name changes both.
Where shallow copying is enough
original = [78, 85]
shallow = original.copy()
shallow.append(60)
shallow[0] = 90
print("original:", original)
print("shallow: ", shallow)
With numbers and text, a shallow copy behaves exactly as you would hope — those values cannot be changed in place.
Deep Copy
copy.deepcopy() copies the inside too, as far down as it goes.
A deep copy copies all the way down
import copy
original = [[78, 85], [60, 92]]
deep = copy.deepcopy(original)
deep[0].append(99)
print("original:", original)
print("deep: ", deep)
list.copy(), copy.copy() and copy.deepcopy()
| Form | Copies | Use when |
|---|---|---|
other = original | nothing — one list, two names | you want them linked |
original.copy() | the list, not the items | items are numbers or text |
original[:] | the same as .copy() | shorter, older style |
list(original) | the same as .copy() | also converts other collections |
copy.copy(x) | the same, for any type | the type may not be a list |
copy.deepcopy(x) | everything, all the way down | the list holds lists or objects |
All three, on a nested list
import copy
original = [[78, 85], [60]]
linked = original
shallow = original.copy()
deep = copy.deepcopy(original)
original[0].append(99)
print("linked: ", linked)
print("shallow:", shallow)
print("deep: ", deep)
Common Pitfalls
A copy taken too late
marks = [78, 85]
def add_bonus(values):
values.append(5)
return values
result = add_bonus(marks)
print("returned:", result)
print("original:", marks)
The function changed the caller's list, because it received a reference. Copy at the start if you mean to leave it alone:
Copying at the door
marks = [78, 85]
def add_bonus(values):
values = list(values)
values.append(5)
return values
result = add_bonus(marks)
print("returned:", result)
print("original:", marks)
Use deepcopy only when the list really holds other lists or objects. It is slow on large structures, and for numbers and text it buys nothing a plain .copy() does not give you.
List Methods
You have now met every list method one at a time. Here they are working together, so you can see the full set:
Every method that changes the list
marks = [78]
marks.append(85)
print("append:", marks)
marks.extend([60, 92])
print("extend:", marks)
marks.insert(1, 100)
print("insert:", marks)
marks.remove(100)
print("remove:", marks)
print("pop: ", marks.pop(), marks)
marks.reverse()
print("reverse:", marks)
marks.sort()
print("sort: ", marks)
marks.clear()
print("clear: ", marks)
The methods that answer a question
marks = [78, 85, 60, 85]
print("count:", marks.count(85))
print("index:", marks.index(85))
print("index from position 2:", marks.index(85, 2))
print("copy:", marks.copy())
| Method | What it does | Gives back |
|---|---|---|
append(x) | adds one item at the end | nothing |
extend(xs) | adds each item of a collection | nothing |
insert(i, x) | inserts before position i | nothing |
remove(x) | deletes the first item equal to x | nothing |
pop(i) | deletes by position | the removed item |
clear() | empties the list | nothing |
copy() | a shallow copy | a new list |
count(x) | how many times x appears | a number |
index(x) | position of the first x | a number |
reverse() | flips the order in place | nothing |
sort() | sorts in place | nothing |
Every method in that table that changes the list returns None. marks = marks.sort() and marks = marks.append(5) both throw the list away.
Built-in Functions with Lists
Lists also work with many of Python's built-in functions:
The built-ins that work on a list
marks = [78, 85, 60, 92]
print("len: ", len(marks))
print("max: ", max(marks))
print("min: ", min(marks))
print("sum: ", sum(marks))
print("sorted:", sorted(marks))
print("list: ", list("Asha"))
zip() walks several collections side by side:
zip
subjects = ["Maths", "Science", "English"]
marks = [78, 85, 60]
print(list(zip(subjects, marks)))
for subject, mark in zip(subjects, marks):
print(subject, mark)
print(dict(zip(subjects, marks)))
zip() stops at the shortest, with no warning:
zip stops at the shortest
print(list(zip([1, 2, 3], ["a", "b"])))
Three more worth knowing:
any, all and a comprehension
marks = [78, 85, 60]
print(all(mark >= 35 for mark in marks))
print(any(mark >= 80 for mark in marks))
print([mark for mark in marks if mark >= 80])
Before moving on, here are some of the mistakes beginners make with lists.
Common mistakes
Each row is one mistake. The middle column is what you will actually see on screen.
| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
marks = marks.sort() | marks becomes None | sort() sorts in place and returns nothing | Call it on its own line |
other = original expecting a copy | Both change together | Assignment copies the reference | original.copy() |
| A shallow copy of a nested list | Inner lists still shared | Only the outer list is copied | copy.deepcopy() |
| Removing while looping | Items quietly skipped | Positions shift under the loop | Build a new list |
[[0] * 3] * 2 | All rows change together | The inner list is repeated, not copied | [[0] * 3 for _ in range(2)] |
append() where extend() was meant | A list added as one item | append adds exactly one thing | extend to add each item |
remove() for a missing value | ValueError | It needs the value to be present | Check with in first |
index() for a missing value | ValueError | index() raises on a miss | Check with in first |
| Using the length as the last index | IndexError | The last index is one less | marks[-1] |
| Changing a list passed into a function | The caller's list changes | It was passed by reference | Copy at the start of the function |
sort() on mixed types | TypeError: '<' not supported | Numbers and text cannot be compared | Keep one type, or use key |
What to Remember
- A list holds many values in order, and can be changed after it is made.
- Items may be of any type and may repeat; a list can hold other lists.
- The first index is 0 and the last is one less than the length.
marks[-1]is the last item. - A slice gives a new list, stops before its second number, and never raises when out of range.
append()adds one item,extend()adds each item of a collection.remove()works by value,pop()by position and hands the item back,delby position and gives nothing.sort()changes the list and returnsNone;sorted()returns a new list.key=decides what to sort by;reverse=Truesorts the other way.- Assignment does not copy.
.copy(),[:]andlist()all make a shallow copy. - A shallow copy shares the inner objects — use
copy.deepcopy()for nested data. - Never add to or remove from a list while looping over it. Build a new one.
[[0] * 3] * 2shares one inner list; use a comprehension instead.
Next: Tuples