New GitHub issue #92628 from pochmann:<br>
<hr>
<pre>
The [Itertools Recipes](https://docs.python.org/3/library/itertools.html#itertools-recipes) have this recipe:
```python
def all_equal(iterable):
"Returns True if all the elements are equal to each other"
g = groupby(iterable)
return next(g, True) and not next(g, False)
```
The `next(g, True)` tries to read a first group and if there isn't one, then... uh... actually `next(g, True)` is *always* true, so that's squeezed into the `return` expression just for the *side effect* of skipping the first group. Psych!
I think for official recipes, it would be better to write clearer code. Two ideas for replacing it:
Proposal 1:
```python
def all_equal(iterable):
"Returns True if all the elements are equal to each other"
g = groupby(iterable)
next(g, None)
return not next(g, False)
```
This makes the first `next` call a statement that clearly indicates we're not using its result and just do it for skipping the first group. I also replaced `True` with `None` to further indicate this.
Proposal 2:
```python
def all_equal(iterable):
"Returns True if all the elements are equal to each other"
g = groupby(iterable)
return not next(g, False) or not next(g, False)
```
This reads as *"There is no first group or there is no second group"*. This actually *uses* the result of the first `next(g, False)`: If it shows that there is no first group, then the function returns `True` immediately. It doesn't even try to look for a second group (unlike the original recipe and my first proposal, which both do). Also, it's nice to have the exact same expression `not next(g, False)` twice.
Really my point is clarity, but for completeness let me also show benchmarks. My first proposal was faster than the original in every case, and the most significant difference is my second proposal being faster for empty iterables.
Python 3.10.4 on Debian (on a Google Compute Engine instance):
```
iterable = ()
235 ns 235 ns 235 ns proposal2 True
265 ns 265 ns 265 ns proposal1 True
265 ns 265 ns 265 ns original True
iterable = (1,)
308 ns 308 ns 308 ns proposal1 True
314 ns 314 ns 314 ns original True
316 ns 316 ns 316 ns proposal2 True
iterable = (1, 2)
361 ns 361 ns 361 ns proposal1 False
366 ns 366 ns 366 ns original False
384 ns 384 ns 384 ns proposal2 False
```
Python 3.10.4 on Windows (my laptop):
```
iterable = ()
926 ns 928 ns 929 ns proposal2 True
1063 ns 1065 ns 1065 ns proposal1 True
1067 ns 1068 ns 1069 ns original True
iterable = (1,)
1225 ns 1228 ns 1230 ns proposal1 True
1251 ns 1253 ns 1253 ns original True
1257 ns 1258 ns 1261 ns proposal2 True
iterable = (1, 2)
1397 ns 1409 ns 1410 ns proposal1 False
1417 ns 1417 ns 1418 ns proposal2 False
1427 ns 1429 ns 1432 ns original False
```
The benchmark code:
```python
from timeit import timeit
from random import shuffle
from bisect import insort
from itertools import groupby
def original(iterable):
"Returns True if all the elements are equal to each other"
g = groupby(iterable)
return next(g, True) and not next(g, False)
def proposal1(iterable):
"Returns True if all the elements are equal to each other"
g = groupby(iterable)
next(g, None)
return not next(g, False)
def proposal2(iterable):
"Returns True if all the elements are equal to each other"
g = groupby(iterable)
return not next(g, False) or not next(g, False)
funcs = [original, proposal1, proposal2]
for iterable in (), (1,), (1, 2):
print(f'{iterable = }')
times = {func: [] for func in funcs}
for _ in range(1000):
shuffle(funcs)
for func in funcs:
number = 1000
t = timeit(lambda: func(iterable), number=number) / number
insort(times[func], t)
for func in sorted(funcs, key=times.get):
print(*('%4d ns ' % round(t * 1e9) for t in times[func][:3]), func.__name__, func(iterable))
print()
```
</pre>
<hr>
<a href="https://github.com/python/cpython/issues/92628">View on GitHub</a>
<p>Labels: docs</p>
<p>Assignee: </p>