157 lines
4.6 KiB
Plaintext
157 lines
4.6 KiB
Plaintext
|
|
An `IndexError` is a bug class that occurs in Python when a program tries to
|
|
access a list, a tuple or other types of sequences at an index that does not
|
|
exist.
|
|
This bug can cause your program to crash or behave unexpectedly.
|
|
To fix an `IndexError`, you should always ensure you are accessing sequences
|
|
within their bounds.
|
|
|
|
== Why is this an issue?
|
|
|
|
An `IndexError` indicates a bug or a logical error in the code.
|
|
|
|
In Python, lists and tuples have a certain length and their elements are indexed
|
|
starting from `0`, counting up to the last index that is still smaller than the
|
|
length.
|
|
When trying to access a list or tuple with an index outside of this range,
|
|
an `IndexError` will be raised and the operation will fail.
|
|
|
|
Negative indices are supported. When using a negative index, it will be
|
|
interpreted by computing the sum of the negative index and the list size.
|
|
The result is then used as the actual index for accessing the sequence.
|
|
Thus, this sum must be non-negative and fit into the aforementioned range.
|
|
|
|
include::../impact.adoc[]
|
|
|
|
== How to fix it
|
|
|
|
=== Code examples
|
|
|
|
The following examples contain out-of-bounds accesses to sequences, resulting in
|
|
an `IndexError`.
|
|
These situations can be avoided by carefully considering the range of valid
|
|
index values, or even better, by comparing indices against the length of a
|
|
sequence.
|
|
|
|
In this first example, the list `ls` contains three elements.
|
|
Since the first element of a list has index `0`, the last valid index is `2`.
|
|
Therefore, an `IndexError` is raised when accessing `ls` at index `3`.
|
|
|
|
==== Noncompliant code example
|
|
|
|
[source,python,diff-id=1,diff-type=noncompliant]
|
|
----
|
|
def fun():
|
|
ls = [1, 2, 3]
|
|
foo(ls[3]) # Noncompliant: the last index of the list ls is 2. Using the index 3 will raise an IndexError.
|
|
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
[source,python,diff-id=1,diff-type=compliant]
|
|
----
|
|
def fun():
|
|
ls = [1, 2, 3]
|
|
foo(ls[2])
|
|
|
|
----
|
|
|
|
Accessing a list with its length as the index is never correct:
|
|
|
|
==== Noncompliant code example
|
|
|
|
[source,python,diff-id=2,diff-type=noncompliant]
|
|
----
|
|
def fun():
|
|
ls = [1, 2, 3]
|
|
print(ls[len(ls)]) # Noncompliant: Indexing starts at 0, hence the list length will always be an invalid index.
|
|
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
[source,python,diff-id=2,diff-type=compliant]
|
|
----
|
|
def fun():
|
|
ls = [1, 2, 3]
|
|
# We can make sure ls is non-empty before trying to access its last element.
|
|
# Also, the index `len(ls) - 1` or just `-1` will correctly select the last
|
|
# element within bounds.
|
|
print("Empty list!" if not ls else ls[-1])
|
|
|
|
----
|
|
|
|
=== How does this work?
|
|
|
|
You should always ensure that you are accessing sequences using indices that are
|
|
within the bounds of the sequence.
|
|
Here are some best practices to follow:
|
|
|
|
Always compare indices against the length of a sequence using `if-else`
|
|
statements or other control flow constructs before accessing elements.
|
|
The length can be retrieved using the built-in `len(...)` function.
|
|
For example, `len(ls)` returns the length of the list `ls`.
|
|
|
|
Use loops to iterate over sequences instead of accessing elements directly.
|
|
For example, `for elem in ls:` iterates over the list `ls`.
|
|
|
|
=== Pitfalls
|
|
|
|
The indices `0`, `len(...) - 1`, or `-1` for the first and last element of a
|
|
sequence are not always valid!
|
|
Make sure the sequence in question is non-empty before accessing these
|
|
indices.
|
|
|
|
=== Going the extra mile
|
|
|
|
Built-in functions like `map()`, `filter()`, and `reduce()` present additional
|
|
ways to operate on sequences without using indices.
|
|
|
|
If you absolutely need to know the index of an element while iterating over a
|
|
sequence, you can use `enumerate()`. It provides you the indices and the
|
|
elements of a sequence during iteration, eliminating the need to manually
|
|
retrieve elements from the sequence using indices.
|
|
|
|
==== Noncompliant code example
|
|
|
|
[source,python,diff-id=3,diff-type=noncompliant]
|
|
----
|
|
for i in range(len(ls)):
|
|
elem = ls[i] # We can eliminate this access by index using enumerate.
|
|
|
|
foo(i, elem)
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
[source,python,diff-id=3,diff-type=compliant]
|
|
----
|
|
for i, elem in enumerate(ls):
|
|
foo(i, elem)
|
|
----
|
|
|
|
== Resources
|
|
|
|
=== Documentation
|
|
|
|
* https://docs.python.org/3/reference/expressions.html#subscriptions[Subscriptions]
|
|
* https://docs.python.org/3/library/exceptions.html#IndexError[IndexError]
|
|
* https://docs.python.org/3/library/functions.html#built-in-functions[Built-ins, including `map`, `filter`, `enumerate`, etc.]
|
|
|
|
ifdef::env-github,rspecator-view[]
|
|
|
|
'''
|
|
== Implementation Specification
|
|
(visible only on this page)
|
|
|
|
=== Message
|
|
|
|
Fix this access on a list element that may trigger an "IndexError".
|
|
Fix this access on a tuple element that may trigger an "IndexError".
|
|
|
|
|
|
'''
|
|
|
|
endif::env-github,rspecator-view[]
|