Skip to content

Commit ad8a891

Browse files
authored
PEP 798: Rework Rationale Section (#4680)
1 parent 01d53a7 commit ad8a891

File tree

1 file changed

+55
-49
lines changed

1 file changed

+55
-49
lines changed

peps/pep-0798.rst

Lines changed: 55 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -97,33 +97,38 @@ equivalent to ``(x async for ait in aits() for x in ait)``.
9797
Rationale
9898
=========
9999

100-
Combining iterable objects together into a single larger object is a common
101-
task. One `StackOverflow post
100+
Combining multiple iterable objects together into a single object is a common
101+
task. For example, one `StackOverflow post
102102
<https://stackoverflow.com/questions/952914/how-do-i-make-a-flat-list-out-of-a-list-of-lists>`_
103-
asking about flattening a list of lists, for example, has been viewed 4.6
104-
million times. Despite this being a common operation, the options currently
105-
available for performing it concisely require levels of indirection that can
106-
make the resulting code difficult to read and understand.
107-
108-
The proposed notation is concise (avoiding the use and repetition of auxiliary
109-
variables) and, we expect, intuitive and familiar to programmers familiar with
110-
both comprehensions and unpacking notation (see :ref:`pep798-examples` for
111-
examples of code from the standard library that could be rewritten more clearly
112-
and concisely using the proposed syntax).
113-
114-
This proposal was motivated in part by a written exam in a Python programming
115-
class, where several students used the notation (specifically the ``set``
116-
version) in their solutions, assuming that it already existed in Python. This
117-
suggests that the notation is intuitive, even to beginners. By contrast, the
118-
existing syntax ``[x for it in its for x in it]`` is one that students often
119-
get wrong, the natural impulse for many students being to reverse the order of
120-
the ``for`` clauses.
121-
122-
Additionally, the comment section of a `Reddit post
103+
asking about flattening a list of lists has been viewed 4.6 million times, and
104+
there are several examples of code from the standard library that perform this
105+
operation (see :ref:`pep798-examples`). While Python provides a means of
106+
combining a small, known number of iterables using extended unpacking from
107+
:pep:`448`, no comparable syntax currently exists for combining an arbitrary
108+
number of iterables.
109+
110+
This proposal represents a natural extension of the language, paralleling
111+
existing syntactic structures: where ``[x, y, z]`` creates a list from a fixed
112+
number of vaues, ``[item for item in items]`` creates a list from an arbitrary
113+
number of values; this proposal extends that notion to the construction of
114+
lists that involve unpacking, making ``[*item for item in items]`` analogous to
115+
``[*x, *y, *z]``.
116+
117+
We expect this syntax to be intuitive and familiar to programmers already
118+
comfortable with both comprehensions and unpacking notation. This proposal was
119+
motivated in part by a written exam in a Python programming class, where
120+
several students used the proposed notation (specifically the ``set`` version)
121+
in their solutions, assuming that it already existed in Python. This suggests
122+
that the notation represents a logical, consistent extension to Python's
123+
existing syntax. By contrast, the existing double-loop version ``[x for it in
124+
its for x in it]`` is one that students often get wrong, the natural impulse
125+
for many students being to reverse the order of the ``for`` clauses. The
126+
intuitiveness of the proposed syntax is further supported by the comment
127+
section of a `Reddit post
123128
<https://old.reddit.com/r/Python/comments/1m607oi/pep_798_unpacking_in_comprehensions/>`__
124-
following the publication of this PEP shows substantial support for the
125-
proposal and further suggests that the syntax proposed here is legible,
126-
intuitive, and useful.
129+
made following the initial publication of this PEP, which demonstrates support
130+
from a broader community.
131+
127132

128133
Specification
129134
=============
@@ -412,9 +417,9 @@ Code Examples
412417
=============
413418

414419
This section shows some illustrative examples of how small pieces of code from
415-
the standard library could be rewritten to make use of this new syntax to
416-
improve concision and readability. The :ref:`pep798-reference` continues to
417-
pass all tests with these replacements made.
420+
the standard library could be rewritten to make use of this new syntax. The
421+
:ref:`pep798-reference` continues to pass all tests with these replacements
422+
made.
418423

419424
Replacing Explicit Loops
420425
------------------------
@@ -430,7 +435,7 @@ need for defining and referencing an auxiliary variable.
430435
comments.extend(token.comments)
431436
return comments
432437

433-
# improved:
438+
# proposed:
434439
return [*token.comments for token in self]
435440

436441
* From ``shutil.py``::
@@ -441,7 +446,7 @@ need for defining and referencing an auxiliary variable.
441446
ignored_names.extend(fnmatch.filter(names, pattern))
442447
return set(ignored_names)
443448

444-
# improved:
449+
# proposed:
445450
return {*fnmatch.filter(names, pattern) for pattern in patterns}
446451

447452
* From ``http/cookiejar.py``::
@@ -452,7 +457,7 @@ need for defining and referencing an auxiliary variable.
452457
cookies.extend(self._cookies_for_domain(domain, request))
453458
return cookies
454459

455-
# improved:
460+
# proposed:
456461
return [
457462
*self._cookies_for_domain(domain, request)
458463
for domain in self._cookies.keys()
@@ -463,8 +468,8 @@ Replacing from_iterable and Friends
463468

464469
While not always the right choice, replacing ``itertools.chain.from_iterable``
465470
and ``map`` can avoid an extra level of redirection, resulting in code that
466-
follows conventional wisdom that comprehensions are more readable than
467-
map/filter.
471+
follows conventional wisdom that comprehensions are generally more readable
472+
than map/filter.
468473

469474
* From ``dataclasses.py``::
470475

@@ -473,7 +478,7 @@ map/filter.
473478
itertools.chain.from_iterable(map(_get_slots, cls.__mro__[1:-1]))
474479
)
475480

476-
# improved:
481+
# proposed:
477482
inherited_slots = {*_get_slots(c) for c in cls.__mro__[1:-1]}
478483

479484
* From ``importlib/metadata/__init__.py``::
@@ -483,23 +488,23 @@ map/filter.
483488
path.search(prepared) for path in map(FastPath, paths)
484489
)
485490

486-
# improved:
491+
# proposed:
487492
return (*FastPath(path).search(prepared) for path in paths)
488493

489494
* From ``collections/__init__.py`` (``Counter`` class)::
490495

491496
# current:
492497
return _chain.from_iterable(_starmap(_repeat, self.items()))
493498

494-
# improved:
499+
# proposed:
495500
return (*_repeat(elt, num) for elt, num in self.items())
496501

497502
* From ``zipfile/_path/__init__.py``::
498503

499504
# current:
500505
parents = itertools.chain.from_iterable(map(_parents, names))
501506

502-
# improved:
507+
# proposed:
503508
parents = (*_parents(name) for name in names)
504509

505510
* From ``_pyrepl/_module_completer.py``::
@@ -510,7 +515,7 @@ map/filter.
510515
for spec in specs if spec
511516
))
512517

513-
# improved:
518+
# proposed:
514519
search_locations = {
515520
*getattr(spec, 'submodule_search_locations', [])
516521
for spec in specs if spec
@@ -520,30 +525,30 @@ Replacing Double Loops in Comprehensions
520525
----------------------------------------
521526

522527
Replacing double loops in comprehensions avoids the need for defining and
523-
referencing an auxiliary variable, reducing clutter.
528+
referencing an auxiliary variable.
524529

525530
* From ``importlib/resources/readers.py``::
526531

527532
# current:
528533
children = (child for path in self._paths for child in path.iterdir())
529534

530-
# improved:
535+
# proposed:
531536
children = (*path.iterdir() for path in self._paths)
532537

533538
* From ``asyncio/base_events.py``::
534539

535540
# current:
536541
exceptions = [exc for sub in exceptions for exc in sub]
537542

538-
# improved:
543+
# proposed:
539544
exceptions = [*sub for sub in exceptions]
540545

541546
* From ``_weakrefset.py``::
542547

543548
# current:
544549
return self.__class__(e for s in (self, other) for e in s)
545550

546-
# improved:
551+
# proposed:
547552
return self.__class__(*s for s in (self, other))
548553

549554

@@ -773,9 +778,9 @@ Beyond the proposal outlined above, the following were also considered:
773778
This strategy would also make unpacking in synchronous and asynchronous
774779
generators behave symmetrically, but it would also be more complex, enough
775780
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.
781+
that asynchronous generator expressions using the unpacking operator should
782+
not adopt semantics similar to ``yield from`` until ``yield from`` is
783+
supported in asynchronous generators more generally.
779784

780785
3. Using ``yield from`` for unpacking in synchronous generator expressions, and
781786
disallowing unpacking in asynchronous generator expressions until they
@@ -817,8 +822,9 @@ this syntax was clear and intuitive, several concerns and potential downsides
817822
were raised as well. This section aims to summarize those concerns.
818823

819824
* **Overlap with existing alternatives:**
820-
While the proposed syntax is arguably clearer and more concise, there are
821-
already several ways to accomplish this same thing in Python.
825+
While the proposed syntax represents a consistent extension to the language
826+
and is likely to result in more-concise code, there are already several ways
827+
to accomplish this same thing in Python.
822828

823829
* **Function call ambiguity:**
824830
Expressions like ``f(*x for x in y)`` may initially appear ambiguous, as it's
@@ -828,13 +834,13 @@ were raised as well. This section aims to summarize those concerns.
828834
may not be immediately obvious.
829835

830836
* **Potential for overuse or abuse:**
831-
Complex uses of unpacking in comprehensions could obscure logic that would be
837+
Complex uses of unpacking in comprehensions could obscure logic that may be
832838
clearer in an explicit loop. While this is already a concern with
833839
comprehensions more generally, the addition of ``*`` and ``**`` may make
834840
particularly complex uses even more difficult to read and understand at a
835841
glance. For example, while these situations are likely rare, comprehensions
836842
that use unpacking in multiple ways can make it difficult to know what's
837-
being unpacked and when: ``f(*(*x for *x, _ in list_of_lists))``.
843+
being unpacked and when, e.g., ``f(*(*x for *x, _ in list_of_lists))``.
838844

839845
* **Unclear limitation of scope:**
840846
This proposal restricts unpacking to the top level of the comprehension

0 commit comments

Comments
 (0)