How to find the index of an item in a Python list
list.index() answers the question in one call. What it does when the item is
not there, when the item appears twice, and what it costs on a large list are
the parts worth knowing.
The answer
colours = ["red", "green", "blue", "green"]
colours.index("green")
colours.index('green') : 1
Note that green appears at index 1 and index 3.
list.index()
returns the first match and stops looking.
When the item is not there
colours.index('purple') -> ValueError: list.index(x): x not in list
It raises. It does not return -1, which is what
str.find() does
for strings — a genuine inconsistency in the standard library and a reliable
source of surprise.
The obvious guard is to check first:
if "purple" in colours:
i = colours.index("purple")
That works, and it walks the list twice: once for the in, once for the
index. Measured over 2000 lookups of the last element in a 10,000-item list:
'in' then .index() : 0.2877 s for 2000 runs
try/except around index : 0.1600 s for 2000 runs
ratio : 1.80x
So the idiomatic Python version is also the faster one:
try:
i = colours.index("purple")
except ValueError:
i = -1
A default, without the try/except
For a one-liner, a generator with next() and a default:
next((i for i, c in enumerate(colours) if c == "purple"), None)
next(generator, None) : None
This form has a second advantage: it matches on a condition, which
.index() cannot do at all. Finding the first record satisfying something is
the case you actually hit:
idx = next((i for i, p in enumerate(people) if p["age"] > 40), None)
first person over 40 is at index 1 -> alan
Every occurrence
[i for i, c in enumerate(colours) if c == "green"]
[1, 3]
enumerate is
the right tool the moment you need more than the first hit.
If you only want the next one after a known position, index() takes start
and stop arguments, the same as a slice:
first : 1
second : 3
It compares with ==, not identity
Worth knowing because it decides what “found” means. Here is a class that claims to equal everything:
weird.index('anything') : 0
More practically, this is why float('nan') behaves oddly:
float('nan') in [nan] : True
[float('nan')].index(float('nan')) -> ValueError
in on the same nan object succeeds because the containment check tries
identity before equality. Two different nan objects are neither identical
nor equal — nan != nan by IEEE 754 — so .index() finds nothing.
When to stop using it
.index() scans from the front, so it is O(n). For one lookup that is fine.
For repeated lookups, build a dict once:
table = {value: i for i, value in enumerate(words)}
list.index() on 50,000 items : 0.0879 s for 200 lookups
dict lookup : 0.000005 s for 200 lookups
ratio : 17227x
Four orders of magnitude. Building the dict costs one pass, and every lookup after that is effectively free.
If the list is sorted, there is a middle option —
bisect, which is O(log n)
and needs no extra memory:
.index() : 0.1681 s bisect : 0.000026 s ratio 6384x
The caveat is that bisect assumes the list is sorted and does not check. On
unsorted input it returns a confident wrong answer.
In a nested list
There is no built-in for this, but the comprehension is short enough:
next(((r, c) for r, row in enumerate(grid) for c, v in enumerate(row) if v == "d"), None)
position of 'd' in [['a', 'b'], ['c', 'd']] : (1, 1)
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).