[Python-ideas] A comprehension scope issue in PEP 572

Guido van Rossum guido at python.org
Fri May 11 09:59:13 EDT 2018


(Note: this is an off-topic side thread, unrelated to assignment
expressions. Inline comment below.)

On Fri, May 11, 2018 at 9:08 AM, Chris Angelico <rosuav at gmail.com> wrote:

> On Fri, May 11, 2018 at 9:15 PM, Nick Coghlan <ncoghlan at gmail.com> wrote:
> > * *maybe* discover that even the above expansion isn't quite accurate,
> and
> > that the underlying semantic equivalent is actually this (one way to
> > discover this by accident is to have a name error in the outermost
> iterable
> > expression):
> >
> >     def _genexp(_outermost_iter):
> >         for x in _outermost_iter:
> >             yield x
> >
> >     _result = _genexp(_outermost_iter)
> >
> > * and then realise that the optimised list comprehension form is
> essentially
> > this:
> >
> >     def _listcomp(_outermost_iter):
> >         result = []
> >         for x in _outermost_iter:
> >             result.append(x)
> >         return result
> >
> >     _result = _listcomp(data)
> >
> > Now that "yield" in comprehensions has been prohibited, you've learned
> all
> > the edge cases at that point
>
> Not quite! You missed one, just because comprehensions aren't weird
> enough yet. AFAIK you can't tell with the list comp, but with the
> genexp you can (by not iterating over it).
>
> >     def _genexp(_outermost_iter):
> >         for x in _outermost_iter:
> >             yield x
> >
> >     _result = _genexp(data)
>
> It's actually this:
>
>      def _genexp(_outermost_iter):
>          for x in _outermost_iter:
>              yield x
>
>      _result = _genexp(iter(_outermost_iter))
>
> I don't think there's anything in the main documentation that actually
> says this, although PEP 289 mentions it in the detaily bits. [1]
>
> ChrisA
>
> [1] https://www.python.org/dev/peps/pep-0289/#the-details
>

I'm not sure this is the whole story. I tried to figure out how often
__iter__ is called in a genexpr. I found that indeed I see iter() is called
as soon as the generator is brought to life, but it is *not* called a
second time the first time you call next(). However the translation you
show has a 'for' loop which is supposed to call iter() again. So how is
this done? It seems the generated bytecode isn't equivalent to a for-loop,
it's equivalent to s while loop that just calls next().

Disassembly of a regular generator:

def foo(a):
  for x in a: yield x

*  2           0 SETUP_LOOP              18 (to 20)*
              2 LOAD_FAST                0 (a)
*              4 GET_ITER*
        >>    6 FOR_ITER                10 (to 18)
              8 STORE_FAST               1 (x)
             10 LOAD_FAST                1 (x)
             12 YIELD_VALUE
             14 POP_TOP
             16 JUMP_ABSOLUTE            6
        >>   18 POP_BLOCK
        >>   20 LOAD_CONST               0 (None)
             22 RETURN_VALUE


But for a generator:

g = (x for x in C())

  1           0 LOAD_FAST                0 (.0)
        >>    2 FOR_ITER                10 (to 14)
              4 STORE_FAST               1 (x)
              6 LOAD_FAST                1 (x)
              8 YIELD_VALUE
             10 POP_TOP
             12 JUMP_ABSOLUTE            2
        >>   14 LOAD_CONST               0 (None)
             16 RETURN_VALUE

Note the lack of SETUP_LOOP and GET_ITER (but otherwise they are identical).

-- 
--Guido van Rossum (python.org/~guido)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180511/bd05666e/attachment-0001.html>


More information about the Python-ideas mailing list