[Python-Dev] Examples for PEP 572

Serhiy Storchaka storchaka at gmail.com
Wed Jul 4 03:51:50 EDT 2018

04.07.18 04:54, Terry Reedy пише:
> Would (f(x),) be faster?

No. Both "for y in [f(x)]" and "for y in (f(x),)" are compiled to the 
same bytecode. Run your microbenchmarks again, the difference is a small 
random variation.


>>> stuff = [[y := f(x), x/y] for x in range(5)]
>>  stuff = [[y, x/y] for x in range(5) for y in [f(x)]]
> Creating an leaky name binding appears to about 5 x faster than 
> iterating a temporary singleton.

With issue32856 be merged, "for var in [expr]" will be compiled to the 
same bytecode as just "var = expr". This is a simple optimization, and 
this is a good kind of changes that increase performance for free, 
without needing users to use a new syntax. It is not merged yet because 
I have doubts that the need in the assignment inside comprehensions is 
worth even such small complication of the compiler (of course PEP 572 
adds much more complications, and not only to the code generator).

If you need to write the above artificial example as a comprehension, 
lets just merge issue32856.


> The 2-argument form of iter is under-remembered and under-used.  The 
> length difference is 8.
>      while (command := input("> ")) != "quit":
>      for command in iter(lambda: input("> "), "quit"):

This doesn't look like a good rationale for such large change as PEP 572.

> I like the iter version, but the for-loop machinery and extra function 
> call makes a minimal loop half a millisecond slower.
> import timeit as ti
> def s():
>      it = iter(10000*'0' + '1')
> def w():
>      it = iter(10000*'0' + '1')
>      while True:
>          command = next(it)
>          if command == '1': break
> def f():
>      it = iter(10000*'0' + '1')
>      for command in iter(lambda: next(it), '1'): pass
> print(ti.timeit('s()', 'from __main__ import s', number=1000))
> print(ti.timeit('w()', 'from __main__ import w', number=1000))
> print(ti.timeit('f()', 'from __main__ import f', number=1000))
> #
> 0.0009702129999999975
> 0.9365254250000001
> 1.5913117949999998

Using partial() makes it faster:

from functools import partial
def p():
     it = iter(10000*'0' + '1')
     for command in iter(partial(next, it), '1'): pass

print(ti.timeit('s()', 'from __main__ import s', number=1000))
print(ti.timeit('w()', 'from __main__ import w', number=1000))
print(ti.timeit('f()', 'from __main__ import f', number=1000))
print(ti.timeit('p()', 'from __main__ import p', number=1000))

> Of course, with added processing of 'command' the time difference 
> disappears.

Yes, and this is why I didn't bother about a tiny overhead of a lambda. 
You can use partial() in a tight performance critical loop. It is even 
faster than a bare while loop.

>>> # Capturing regular expression match objects
>>> # See, for instance, Lib/pydoc.py, which uses a multiline spelling
>>> # of this effect
>>> if match := re.search(pat, text):
>>>     print("Found:", match.group(0))
>>> # The same syntax chains nicely into 'elif' statements, unlike the
>>> # equivalent using assignment statements.
>>> elif match := re.search(otherpat, text):
>>>     print("Alternate found:", match.group(0))
>>> elif match := re.search(third, text):
>>>     print("Fallback found:", match.group(0))
>> It may be more efficient to use a single regular expression which 
>> consists of multiple or-ed patterns
> My attempt resulted in a slowdown.  Duplicating the dominance of pat 
> over otherpat over third requires, I believe, negative lookahead 
> assertions.

I have to admit that *this* example will not get a benefit from 
rewriting with a single regular expression. I was fooled by the 
misleading reference to pydoc.py. The code in pydoc.py doesn't have 
anything common with this example, it searches the first occurrence of 
the set of patterns in a loop, while the example searches patterns 
sequentially and only once. The code similar to pydoc.py is common, but 
I would want to see a real code that corresponds the example in PEP 572.

More information about the Python-Dev mailing list