Python Lists: Creating, Indexing and Slicing
A list is an ordered, changeable sequence that can hold anything. Indexing, slicing and slice assignment cover most of what you will ever do with one.
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
Creating a list
empty = []
empty_too = list()
numbers = [1, 2, 3, 4]
names = ["Meera", "Arun"]
mixed = [1, "two", 3.0, True, None, [5, 6]] # any types, including lists
from_string = list("abc") # ['a', 'b', 'c']
from_range = list(range(5)) # [0, 1, 2, 3, 4]
repeated = [0] * 5 # [0, 0, 0, 0, 0]A list is ordered (positions are meaningful and stable), mutable (it can be changed in place), and allows duplicates. Those three properties decide when a list is the right choice.
Indexing
items = ["a", "b", "c", "d", "e"]
print(items[0]) # a
print(items[2]) # c
print(items[-1]) # e last
print(items[-2]) # d
# print(items[10]) # IndexError: list index out of range 'a' 'b' 'c' 'd' 'e'
0 1 2 3 4
-5 -4 -3 -2 -1Changing an item
items = ["a", "b", "c"]
items[1] = "B"
print(items) # ['a', 'B', 'c']This is the difference from a string. A list supports item assignment because it is mutable.
Slicing
items = [0, 1, 2, 3, 4, 5, 6, 7]
print(items[2:5]) # [2, 3, 4] stop is excluded
print(items[:3]) # [0, 1, 2]
print(items[5:]) # [5, 6, 7]
print(items[:]) # a shallow copy of the whole list
print(items[-3:]) # [5, 6, 7]
print(items[::2]) # [0, 2, 4, 6]
print(items[::-1]) # [7, 6, 5, 4, 3, 2, 1, 0] reversed
print(items[10:20]) # [] slicing never raisesSlice assignment
A slice can be assigned to, which lists allow and strings do not. It can replace, insert or delete, and the lengths do not have to match.
items = [0, 1, 2, 3, 4]
items[1:3] = ["a", "b"] # replace, same length
print(items) # [0, 'a', 'b', 3, 4]
items[1:3] = ["x"] # replace with fewer: the list shrinks
print(items) # [0, 'x', 3, 4]
items[1:1] = ["p", "q"] # empty slice: insert without removing
print(items) # [0, 'p', 'q', 'x', 3, 4]
items[1:3] = [] # delete a range
print(items) # [0, 'x', 3, 4]
items[:] = [9, 9] # replace the CONTENTS, keeping the same object
print(items) # [9, 9]items[:] = otheranditems = otherlook similar and behave completely differently. The first changes the existing list, so every other name pointing at it sees the change. The second rebinds only this name.
Length, membership and counting
items = ["a", "b", "a", "c"]
print(len(items)) # 4
print("a" in items) # True
print("z" not in items) # True
print(items.count("a")) # 2
print(items.index("b")) # 1
# print(items.index("z")) # ValueErrorIterating
items = ["a", "b", "c"]
for item in items:
print(item)
for index, item in enumerate(items):
print(index, item)
for item in reversed(items):
print(item)
for item in sorted(items, reverse=True):
print(item)Lists hold references
inner = [1, 2]
outer = [inner, inner]
inner.append(3)
print(outer) # [[1, 2, 3], [1, 2, 3]] - both entries are the same objectA list does not contain objects; it contains references to them. This is why the repetition trick for building a grid fails:
grid = [[0] * 3] * 2 # WRONG
grid[0][0] = 9
print(grid) # [[9, 0, 0], [9, 0, 0]] - one row, referenced twice
grid = [[0] * 3 for _ in range(2)] # correct: a fresh row each time
grid[0][0] = 9
print(grid) # [[9, 0, 0], [0, 0, 0]]Useful built ins
numbers = [4, 1, 9, 3]
print(len(numbers)) # 4
print(sum(numbers)) # 17
print(min(numbers), max(numbers)) # 1 9
print(sorted(numbers)) # [1, 3, 4, 9] - a NEW list
print(list(reversed(numbers)))# [3, 9, 1, 4]
print(any(n > 8 for n in numbers)) # True
print(all(n > 0 for n in numbers)) # TrueUnpacking
point = [3, 7]
x, y = point
print(x, y) # 3 7
first, *rest = [1, 2, 3, 4]
print(first, rest) # 1 [2, 3, 4]
head, *middle, tail = [1, 2, 3, 4, 5]
print(middle) # [2, 3, 4]
a, b = [1, 2]
a, b = b, a # swap
print(a, b) # 2 1Common mistakes
- Assuming
items[2:5]includes index 5. - Using
[[0] * 3] * 2to build a grid. - Assuming
b = acopies a list. - Calling
.index()on a value that may be missing, without catchingValueError. - Indexing out of range instead of slicing, when an empty result would have been fine.
- Confusing
items[:] = otherwithitems = other.
Best practices
- Use a list when order matters and the contents will change; use a tuple when they will not.
- Use negative indexes rather than
len(items) - 1. - Use slicing rather than a loop to take a portion.
- Build nested lists with a comprehension, never with repetition.
- Prefer
inover.index()when you only need to know whether something is present.
Practice
- Given
list(range(10)), produce the even values, the last three, and the whole list reversed, using slices only. - Insert two items into the middle of a list without using
insert. - Explain the difference between
items[:] = [1, 2]anditems = [1, 2]when another name refers to the same list. - Build a 3 by 4 grid of zeros correctly, then set one cell and prove the others did not change.
- Write a function returning the second largest distinct value in a list.
Conclusion
Lists are ordered, mutable sequences of references. Indexing reaches one item, slicing reaches a range and never raises, and slice assignment can replace, insert or delete in one statement. The reference nature is what makes copying and grid building worth thinking about.