Amazingly, next's fallback value second parameter mentioned in this comment in a duplicate thread hasn't been shown here yet.
The basic .index() works well when you can compare whole objects, but it's common to need to search a list of objects or dicts for a particular item by a certain property, in which case a generator with a condition is the natural choice:
>>> users = [{"id": 2, "name": "foo"}, {"id": 3, "name": "bar"}] >>> target_id = 2 >>> found_user = next(x for x in users if x["id"] == target_id) >>> found_user {'id': 2, 'name': 'foo'} This stops at the first matching element and is reasonably succinct.
However, if no matching element wasis found, it raises a StopIteration error is raised, which is a little awkward to deal with. Luckily, next offers a second parameter next(gen, Nonedefault) fallback to provide a more natural, except-free control flow:
>>> found_user = next((x for x in users if x["id"] == target_id), None) >>> if not found_user: ... print("user not found") ... user not found This is a bit more verbose, but still fairly readable.
If an index is desired:
>>> found_idx = next((i for i, x in enumerate(users) if x["id"] == 1), None) >>> found_idx None >>> next((i for i, x in enumerate(users) if x["id"] == 3), None) 1 As this comment points out, it may be best not to return the typical -1 for a missing index, since that's a valid index in Python. Raising is appropriate if None seems odd to return.
These are a bit verbose, but feel free to bury the code in a helper function if you're using it repeatedly, providing an arbitrary predicate.
>>> def find(it, pred): ... return next((x for x in it if pred(x)), None) ... >>> find(users, lambda user: user["id"] == 2) {'id': 2, 'name': 'foo'} >>> print(find(users, lambda user: user["id"] == 42)) None >>> find("foobAr", str.isupper) # works on any iterable! 'A'