[Python-ideas] Re: Function suggestion: itertools.one()

2020-07-28 Thread Noam Yorav-Raphael
Thanks! It's good to hear that you too find it useful.

Since adding methods to built-in types is much heavier than adding one
function to a module, l suggest keeping this discussion focused on adding
just the one() function to itertools, and see if there is enough support
for this.

Cheers,
Noam

On Tue, Jul 28, 2020 at 10:47 PM Alex Hall  wrote:

> 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 
> 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-...@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, c

[Python-ideas] Function suggestion: itertools.one()

2020-07-28 Thread Noam Yorav-Raphael
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/