@@ -742,11 +742,12 @@ do not support ``yield from`` (see the section of :pep:`525` on Asynchronous
742742The core question centered around whether sync and async generator expressions
743743should use ``yield from `` (or an equivalent) when unpacking, as opposed to an
744744explicit loop. The main difference between these options is whether the
745- resulting generator delegates to the objects being unpacked (see :pep: `380 `),
746- which would affect the behavior of these generator expressions when used with
747- ``.send()/.asend() ``, ``.throw()/.athrow() ``, and ``.close()/.aclose() `` in
748- the case where the objects being unpacked are themselves generators, which
749- is unlikely to be a common use case.
745+ resulting generator delegates to the objects being unpacked, which would affect
746+ the behavior of these generator expressions when used with
747+ ``.send()/.asend() ``, ``.throw()/.athrow() ``, and ``.close()/.aclose() `` in the
748+ case where the objects being unpacked are themselves generators. The
749+ differences between these options are summarized in
750+ :ref: `pep798-appendix-yieldfrom `.
750751
751752Several reasonable options were considered, none of which was a clear winner in
752753a `poll in the Discourse thread
@@ -759,21 +760,22 @@ Beyond the proposal outlined above, the following were also considered:
759760 This strategy would have resulted in a symmetry between synchronous and
760761 asynchronous generator expressions but would have prevented a
761762 potentially-useful tool by disallowing delegation in the case of synchronous
762- generator expressions. Moreover, the existing asymmetry between synchronous
763- and asynchronous generators mitigates concerns about asymmetry of the
764- unpacking operator within generator expressions.
763+ generator expressions. One specific concern with this approach is the
764+ introduction of an asymmetry between synchronous and asynchronous
765+ generators, but this concern is mitigated by the fact that these asymmetries
766+ already exist between synchronous and asynchronous generators more
767+ generally.
765768
7667692. Using ``yield from `` for unpacking in synchronous generator expressions and
767770 mimicking the behavior of ``yield from `` for unpacking in async generator
768771 expressions.
769772
770773 This strategy would also make unpacking in synchronous and asynchronous
771774 generators behave symmetrically, but it would also be more complex, enough
772- so that the cost may not be worth the benefit, particularly in the absence
773- of a compelling practical use case for delegating to subgenerators during
774- unpacking. Generator expressions using the unpacking operator should not
775- use semantics similar to ``yield from `` until ``yield from `` is supported
776- in asynchronous generators more generally.
775+ so that the cost may not be worth the benefit. As such, this PEP proposes
776+ that generator expressions using the unpacking operator should not use
777+ semantics similar to ``yield from `` until ``yield from `` is supported in
778+ asynchronous generators more generally.
777779
7787803. Using ``yield from `` for unpacking in synchronous generator expressions, and
779781 disallowing unpacking in asynchronous generator expressions until they
@@ -783,8 +785,8 @@ Beyond the proposal outlined above, the following were also considered:
783785 expressions do gain support for ``yield from `` in the future by making sure
784786 that any decision made at that point would be fully backwards-compatible.
785787 But the utility of unpacking in that context seems to outweigh the potential
786- downside of a backwards-incompatible change in the future if async generator
787- expressions do receive support for ``yield from ``.
788+ downside of a minimally-invasive backwards-incompatible change in the future
789+ if async generator expressions do receive support for ``yield from ``.
788790
7897914. Disallowing unpacking in all generator expressions.
790792
@@ -793,13 +795,14 @@ Beyond the proposal outlined above, the following were also considered:
793795
794796
795797Each of these options (including the one presented in this PEP) has its
796- benefits and drawbacks, with no option being clearly superior on all fronts;
797- but the semantics proposed in :ref: `pep798-genexpsemantics ` represent a
798- reasonable compromise where unpacking in both synchronous and asynchronous
799- generator expressions mirrors common ways of writing equivalent generators
800- currently.
801-
802- As suggested above, though, this decision should be revisited in the event that
798+ benefits and drawbacks, with no option being clearly superior on all fronts.
799+ The semantics proposed in :ref: `pep798-genexpsemantics ` represent a reasonable
800+ compromise where unpacking in both synchronous and asynchronous generator
801+ expressions mirrors common ways of writing equivalent generators currently.
802+ Moreover, these subtle differences are unlikely to be impactful for the common
803+ use case of combining simple collections.
804+
805+ As suggested above, this decision should be revisited in the event that
803806asynchronous generators receive support for ``yield from `` in the future, in
804807which case adjusting the semantics of unpacking in async generator expressions
805808to use ``yield from `` should be considered.
@@ -842,8 +845,9 @@ were raised as well. This section aims to summarize those concerns.
842845 for maintainers of code formatters, linters, type checkers, etc., to make
843846 sure that the new syntax is supported.
844847
845- Other Languages
846- ===============
848+
849+ Appendix: Other Languages
850+ =========================
847851
848852Quite a few other languages support this kind of flattening with syntax similar
849853to what is already available in Python, but support for using unpacking syntax
@@ -912,6 +916,149 @@ Civet, making use of JavaScript's ``...`` syntax for unpacking:
912916
913917 for xs of [[1 ,2 ,3 ], [], [4 ,5 ]] then ... (xs++ xs)
914918
919+ .. _pep798-appendix-yieldfrom :
920+
921+ Appendix: Semantics of Generator Delegation
922+ ===========================================
923+
924+ One of the common questions about the semantics outlined above had to do with
925+ the difference between using ``yield from `` when unpacking inside of a
926+ generator expression, versus using an explicit loop. Because this is a
927+ fairly-advanced feature of generators, this appendix attempts to summarize some
928+ of the key differences between generators that use ``yield from `` versus those
929+ that use explicit loops.
930+
931+ Basic Behavior
932+ --------------
933+
934+ For simple iteration over values, which we expect to be by far the most-common
935+ use of unpacking in generator expressions, both approaches produce identical
936+ results::
937+
938+ def yield_from(iterables):
939+ for iterable in iterables:
940+ yield from iterable
941+
942+ def explicit_loop(iterables):
943+ for iterable in iterables:
944+ for item in iterable:
945+ yield item
946+
947+ # Both produce the same sequence of values
948+ x = list(yield_from([[1, 2], [3, 4]]))
949+ y = list(explicit_loop([[1, 2], [3, 4]]))
950+ print(x == y) # prints True
951+
952+ Advanced Generator Protocol Differences
953+ ---------------------------------------
954+
955+ The differences become apparent when using the advanced generator protocol
956+ methods ``.send() ``, ``.throw() ``, and ``.close() ``, and when the sub-iterables
957+ are themselves generators rather than simple sequences.
958+
959+ Delegation with ``.send() ``
960+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
961+ .. code :: python
962+
963+ def sub_generator ():
964+ x = yield " first"
965+ yield f " received: { x} "
966+ yield " last"
967+
968+ def yield_from ():
969+ yield from sub_generator()
970+
971+ def explicit_loop ():
972+ for item in sub_generator():
973+ yield item
974+
975+ # With yield from, values are passed through to sub-generator
976+ gen1 = yield_from()
977+ print (next (gen1)) # prints "first"
978+ print (gen1.send(" hello" )) # prints "received: hello"
979+ print (next (gen1)) # prints "last"
980+
981+ # With explicit loop, .send() affects the outer generator; values don't reach the sub-generator
982+ gen2 = explicit_loop()
983+ print (next (gen2)) # prints "first"
984+ print (gen2.send(" hello" )) # prints "received: None" (sub-generator receives None instead of "hello")
985+ print (next (gen2)) # prints "last"
986+
987+ Exception Handling with ``.throw() ``
988+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
989+
990+ .. code :: python
991+
992+ def sub_generator_with_exception_handling ():
993+ try :
994+ yield " first"
995+ yield " second"
996+ except ValueError as e:
997+ yield f " caught: { e} "
998+
999+ def yield_from ():
1000+ yield from sub_generator_with_exception_handling()
1001+
1002+ def explicit_loop ():
1003+ for item in sub_generator_with_exception_handling():
1004+ yield item
1005+
1006+ # With yield from, exceptions are passed to sub-generator
1007+ gen1 = yield_from()
1008+ print (next (gen1)) # prints "first"
1009+ print (gen1.throw(ValueError (" test" ))) # prints "caught: test"
1010+
1011+ # With explicit loop, exceptions affect the outer generator only
1012+ gen2 = explicit_loop()
1013+ print (next (gen2)) # prints "first"
1014+ print (gen2.throw(ValueError (" test" ))) # ValueError is raised; sub-generator doesn't see it
1015+
1016+ Generator Cleanup with ``.close() ``
1017+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1018+
1019+ .. code :: python
1020+
1021+ # hold references to sub-generators so GC doesn't close the explicit loop version
1022+ references = []
1023+
1024+ def sub_generator_with_cleanup ():
1025+ try :
1026+ yield " first"
1027+ yield " second"
1028+ finally :
1029+ print (" sub-generator received GeneratorExit" )
1030+
1031+ def yield_from ():
1032+ try :
1033+ g = sub_generator_with_cleanup()
1034+ references.append(g)
1035+ yield from g
1036+ finally :
1037+ print (" outer generator received GeneratorExit" )
1038+
1039+ def explicit_loop ():
1040+ try :
1041+ g = sub_generator_with_cleanup()
1042+ references.append(g)
1043+ for item in g:
1044+ yield item
1045+ finally :
1046+ print (" outer generator received GeneratorExit" )
1047+
1048+ # With yield from - GeneratorExit is passed through to sub-generator
1049+ gen1 = yield_from()
1050+ print (next (gen1)) # prints "first"
1051+ gen1.close() # closes sub-generator and then outer generator
1052+
1053+ # With explicit loop - GeneratorExit goes to outer generator first
1054+ gen2 = explicit_loop()
1055+ print (next (gen2)) # prints "first"
1056+ gen2.close() # only closes outer generator
1057+
1058+ print (' program finished; GC will close the explicit loop subgenerator' )
1059+ # second inner generator closes when GC closes it at the end
1060+
1061+
9151062 References
9161063==========
9171064
0 commit comments