Add itertools functions for ugly idioms

first_match

Getting the first item in an Iterable can be ugly and unintuitive in python.

I feel it would be nice for people who prefer a more functional style to have an option such as:

def first_match[T,U](it: Iterable[T], pred: Callable[[T], bool], default: U = None) -> T | U:
    """
    Returns the first item in an `Iterable` that matches a predicate
    else a default value.
    """
    return next((x for x in it if pred(x)), default)

walk

Sometimes you have a generator of stateful functions calls that you want to expand without the overhead of using a list comprehension.

something like :

def walk(it: Iterable) -> None:
    """
    Traverse an iterable, discarding any results.
    Used to expand generators of stateful function calls.
    """
    for _ in it:
        pass

I am much less certain about this one. There may already be a better way of doing this that I am unaware of.

Obviously it would need a better name too.

What do people think about these?

There are ok/good solutions for both of these:

next(filter(pred, it), default)

And:

walk = collections.deque(maxlen=0).extend

See a more general consumer recipe in itertools — Functions creating iterators for efficient looping — Python 3.12.4 documentation, which is implemented in more-itertools · PyPI

4 Likes

IMO, these are very much in the realm of “not every 3-line function should be in the stdlib”.

next((x for x in it if pred(x)), default) seems perfectly understandable to me, and if you do feel that it needs a bit of an explanation, a comment can be added. Or define your first_match function as a helper at the top of the file.

Similarly, for _ in it: pass is a reasonably well known idiom, easy to work out what it does, and you can add a comment or encapsulate it in a helper if you feel it’s worthwhile.

Honestly, for either of these, I think it would be more effort to find the relevant function in the stdlib and confirm it worked the way you wanted, than to write the code inline.

As a further note, your walk function has been proposed a number of times before. It’s available in more-itertools as consume.

17 Likes

Having just found this comment of mine again, I will say that 3 weeks later, I did find next((x for x in it if pred(x)), default) took a bit of thinking about to remember what it was doing. If I were writing this in a real application I would definitely add a comment (or maybe define first_match somewhere in my code base - but note that most predicates are likely to be expressions, rather than existing functions, so first_match won’t be quite as convenient as the neat pred argument suggests).

Nevertheless, I still don’t think this warrants being in the stdlib.

1 Like

I would support the inclusion of a link to more_itertools in a “see also” section of the docs of itertools. There is precedent for linking to 3rd party resources in this way, e.g. the docs the array module link to NumPy.

1 Like

Good news - such a link to more_itertools is there right now!

1 Like

That’s not news, has been there for years.

(Maybe Mark had literally searched the page for “more_itertools” instead of “more-itertools”?)

>>> import collections
>>> collections.deque(maxlen=0).expand
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'collections.deque' object has no attribute 'expand'. Did you mean: 'extend'?

Yes yes, extend it should be :slight_smile: Corrected, thank you