question about generators

Tim Peters tim at zope.com
Thu Aug 15 17:08:59 EDT 2002


[Andrew Koenig]
> ...
> Yield actually does one of two very different things depending
> on context.

I expect that's been cleared up by now, so I'll skip a repetition.

> ...
> These facts mean that yield statements break a form of abstraction
> that is commonplace in many other contexts: the ability to take a
> collection of statements and put them in a function.  For example:
>
>         def f():
>                 <statement 1>
>                 <statement 2>
>                 <statement 3>
>                 <statement 4>
>
> Under ordinary circumstances, I can rewrite this as
>
>         def f():
>                 <statement 1>
>                 g()
>                 <statement 4>
>
>         def g():
>                 <statement 2>
>                 <statement 3>
>
> without changing the meaning of the program (provided that statement 2
> and statement 3 do not refer to local variables of f).

More relevant I think is that 2 and 3 must not affect control flow in ways
that change as a result of the move.  For example, if 2 is "return", the
"before" and "after" spellings say quite different things.

> However, if I use yield statements,

Among many other control-flow statements, yes <wink>.

> the abstraction breaks down:

>         def f():
>                 yield 1
>                 yield 2
>                 yield 3
>                 yield 4
>
> is not equivalent to
>
>         def f():
>                 yield 1
>                 g()
>                 yield 4
>
>         def g():
>                 yield 2
>                 yield 3
>
> I think that Tim's "yield every g()" notion is really a way of saying
> ``I want to call g, but I want every yield in g and anything it calls
> to be considered type 2, not type 1.''

I don't buy the type 1-vs-2 distinction (all yields are "type 2" in Python);

    yield every x

is intended as pure sugar for

    for _temp_var in x:
        yield _temp_var

A

    yield every g()

in your "after" f() would make it quite like the "before" spelling.

If you haven't played with the Icon language, you should!  It's a lot of
fun, and it's very educational to program in a language where generators are
ubiquitous (all expressions in Icon are generators, although many of them--
like, say, the integer literal 42 --yield a sequence with only one value).
What I'm calling "yield every" here is closest to what's called "suspend" in
Icon; Icon also has an "every" control structure, which forces its
expression argument to generate all its results, but without any notion of
returning them *to* "a caller".  So, e.g.,

    every f((1 to 3) + (5 to 10))

calls the function f() 18 times, once each for the 18 sums 1+5, 1+6, ...,
3+10.  OK, I'm lying, it may do a lot more than that, if "f" is an
expression that happens to generate multiple callables too.  Like

    every (f|g|h)(42)

calls f(42) then g(42) then h(42).

    suspend (f|g|h)(42)

does the same, but also yields each function-call result to the current
caller.

Generators weren't meant to be central to Python programming, though, and it
took a lot of discipline to keep the Simple Generators PEP simple <0.7
wink>.





More information about the Python-list mailing list