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() and sorted()
  • 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

CharacteristicWhat it means
Ordereditems keep the order you put them in
Indexedthe first item is at 0
Changeableyou can replace, add and remove items
Duplicates allowedthe same value can appear many times
Any typeitems need not match
Nestablea 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()

FormCopiesUse when
other = originalnothing — one list, two namesyou want them linked
original.copy()the list, not the itemsitems 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 typethe type may not be a list
copy.deepcopy(x)everything, all the way downthe 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())
MethodWhat it doesGives back
append(x)adds one item at the endnothing
extend(xs)adds each item of a collectionnothing
insert(i, x)inserts before position inothing
remove(x)deletes the first item equal to xnothing
pop(i)deletes by positionthe removed item
clear()empties the listnothing
copy()a shallow copya new list
count(x)how many times x appearsa number
index(x)position of the first xa number
reverse()flips the order in placenothing
sort()sorts in placenothing

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.

MistakeWhat you seeWhy it happensFix
marks = marks.sort()marks becomes Nonesort() sorts in place and returns nothingCall it on its own line
other = original expecting a copyBoth change togetherAssignment copies the referenceoriginal.copy()
A shallow copy of a nested listInner lists still sharedOnly the outer list is copiedcopy.deepcopy()
Removing while loopingItems quietly skippedPositions shift under the loopBuild a new list
[[0] * 3] * 2All rows change togetherThe inner list is repeated, not copied[[0] * 3 for _ in range(2)]
append() where extend() was meantA list added as one itemappend adds exactly one thingextend to add each item
remove() for a missing valueValueErrorIt needs the value to be presentCheck with in first
index() for a missing valueValueErrorindex() raises on a missCheck with in first
Using the length as the last indexIndexErrorThe last index is one lessmarks[-1]
Changing a list passed into a functionThe caller's list changesIt was passed by referenceCopy at the start of the function
sort() on mixed typesTypeError: '<' not supportedNumbers and text cannot be comparedKeep 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, del by position and gives nothing.
  • sort() changes the list and returns None; sorted() returns a new list.
  • key= decides what to sort by; reverse=True sorts the other way.
  • Assignment does not copy. .copy(), [:] and list() 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] * 2 shares one inner list; use a comprehension instead.

Next: Tuples