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-...@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-le...@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/

Reply via email to