In my personal toolbox of utility functions, this is by far the function I use most often, although it's implemented slightly differently and I call it `only`. I think it's very useful and it would be great to have in the standard library to encourage people to write safer code.

Often this is part of a larger expression, especially if I'm drilling into some nested data structure. This can lead to having a prefix operation (the function call) breaking a chain of postfix operations (attributes, method calls, subscripting...) which is ugly and less readable. It would be nice if this could also be available as a method on lists, tuples, and sets to keep the data flowing left to right. Plus it would save an import.

On Tue, Jul 28, 2020 at 9:29 PM Noam Yorav-Raphael <noamraph@gmail.com> wrote:
Hello,

There's a simple function that I use many times, and I think may be a good fit to be added to itertools. A function that gets an iterator, and if it has exactly one element returns it, and otherwise raises an exception. This is very useful for cases where I do some sort of query that I expect to get exactly one result, and I want an exception to be raised if I'm wrong. For example:

jack = one(p for p in people if p.id == '1234')

sqlalchemy already has such a function for queries: https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.one

more-itertools has this exact function:
https://more-itertools.readthedocs.io/en/latest/api.html#more_itertools.one

Here is a simple implementation:

def one(iterable):
    it = iter(iterable)
    try:
        first = next(it)
    except StopIteration:
        raise ValueError("Iterator is empty")
    try:
        second = next(it)
    except StopIteration:
        return first
    else:
        raise ValueError("Iterator has more than one item")

I brought this up on python-dev, but it should be discussed here.
Here is the discussion:
https://mail.python.org/archives/list/python-dev@python.org/thread/D52MPKLIN4VEXBOCKVMTWAK66MAOEINY/

Brett Cannon said that this idea has been brought up at least twice before. I found:
https://mail.python.org/archives/list/python-ideas@python.org/thread/FTJ6JRDTZ57HUVZ3PVIZV2NHU2NLAC4X/#RMWV3SNZ2N4KZLPKPIDE42H46QDEIVHE

https://mail.python.org/archives/list/python-ideas@python.org/thread/REYDJFCXQNQG4SAWKELQMCGM77IZG47Q/#ITR2ILPVCKYR52U2D7RHGENASZTNVDHN

The first thread hasn't reached any operative conclusion. The second thread was very long, and seemed to focus mostly on another function, first(), that doesn't check if there is more than one item. Joao S. O Bueno said that it passed "general approval". I think that perhaps a new discussion, focused just on one (no pun intended) function in the itertools module may reach a conclusion. 

It was suggested that instead of an additional function, one can use iterator unpacking:

jack, = (p for p in people if p.id == '1234')
or
[jack] = (p for p in people if p.id == '1234')

I still think that having a one() function would be useful, since:
1. I think it spells the intention more clearly. It is not symbols that you need to understand their meaning in order to understand that I expect the iterable to have exactly one item, it's spelled in code.
2. The exception would be easier to understand, since errors in tuple unpacking usually mean something else.
3. The one() function allows you to use the result inside an expression without assigning it to a variable. Therefore, I think it encourages writing better code. It's very easy to write:
    print([p for p in people if p.id == '1234][0])
(which has the problem of not verifying the assumption that there's no more than one result), and I find it easier to replace _[0] with one(_) than to be required to name a new variable, and instead of having an operation on the iterable, change the way I'm assigning to it.

WDYT?

Cheers,
Noam


_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/6OLEL4XTUWXRI7ENODKEDOYFBRVDYKI7/
Code of Conduct: http://python.org/psf/codeofconduct/